size_t或int用于尺寸,索引等


15

在C ++中,size_t(或更正确地说T::size_type,通常是“ 类型” size_t;即unsigned类型)被用作的返回值size(),的自变量operator[]等(请参见std::vector等)。

另一方面,.NET语言出于相同目的使用int(并且(可选long))。实际上,不需要 CLS兼容语言来支持unsigned类型

由于.NET是比C ++新的东西告诉我,可能会有问题,使用unsigned int连供的事情,“不可能”像数组索引或长度为负。C ++的方法是否“向后兼容”?还是这两种方法之间存在真实而重大的设计折衷?

为什么这么重要?好吧……对于C ++中的新多维类,我应该使用什么?size_t还是int

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
值得注意的是:.NET Framework中的多个地方,-1都是从返回索引的函数返回的,以指示“未找到”或“超出范围”。它也从Compare()函数(实现IComparable)中返回。32位int被认为是通用数字的输入类型,我希望这是显而易见的原因。
罗伯特·哈维

Answers:


9

鉴于.NET比C ++更新,某事告诉我,即使对于“不可能”为负数(例如数组索引或长度)的东西,使用unsigned int可能也会出现问题。

是。对于某些类型的应用程序,例如图像处理或数组处理,通常需要访问相对于当前位置的元素:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

在这些类型的应用程序中,如果不仔细考虑,就无法对无符号整数执行范围检查:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

相反,您必须重新排列范围检查表达式。那是主要的区别。程序员还必须记住整数转换规则。如有疑问,请重新阅读http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions

许多应用程序不需要使用非常大的数组索引,但是它们确实需要执行范围检查。此外,许多程序员没有经过训练来进行这种表情重新安排体操。一个错失的机会打开了利用的大门。

C#实际上是为那些不需要每个数组超过2 ^ 31个元素的应用程序而设计的。例如,电子表格应用程序不需要处理那么多的行,列或单元格。C#通过具有可选的经过检查的算法来处理上限,该算法可以使用关键字为代码块启用,而不会弄乱编译器选项。因此,C#支持使用有符号整数。当完全考虑这些决定时,这是很有意义的。

C ++完全不同,并且更难获得正确的代码。

关于允许使用带符号的算术消除潜在的“最小惊讶原则”违背的实际重要性,一个典型的例子是OpenCV,它使用带符号的32位整数作为矩阵元素索引,数组大小,像素通道数等。处理是编程域中大量使用相对数组索引的示例。无符号整数下溢(环绕负结果)将使算法的实现复杂化。


这正是我的情况;感谢您的具体示例。(是的,我知道这一点,但是引用“上级主管”会很有用。)
2016年

1
@丹:如果你需要引用一些东西,这篇文章会更好。
rwong

1
@丹:约翰·雷格尔(John Regehr)正在积极地以编程语言研究这一问题。参见blog.regehr.org/archives/1401
rwong


14

答案确实取决于谁将使用您的代码,以及他们希望看到哪些标准。

size_t 是具有目的的整数大小:

该类型size_t是实现定义的无符号整数类型,该类型足够大以包含任何对象的字节大小。(C ++ 11规范18.2.6)

因此,任何时候只要要使用字节大小的对象,都应使用size_t。现在,在许多情况下,您并没有使用这些维度/索引来计数字节,但是大多数开发人员选择使用size_t保持一致性。

请注意,如果您的类打算具有STL类的外观,则应始终使用size_t。规范中的所有STL类都使用size_t 编译器将typedef size_t设置为unsigned int有效,并且将其定义为unsigned long。如果您使用intlong直接直接,最终将遇到编译器,在该编译器中,一个认为您的班级遵循STL风格的人会因为您未遵循标准而被困住。

至于使用带符号的类型,有一些优点:

  • 较短的名称-人们输入起来确实很容易int,但是很难使代码混乱unsigned int
  • 每个大小一个整数-只有一个符合CLS的32位整数,即Int32。在C ++中,有两个(int32_tuint32_t)。这可以简化API的互操作性

有符号类型的最大缺点是显而易见的一个缺点:您丢失了一半的域。一个带符号的数字不能等于一个没有符号的数字。当C / C ++出现时,这非常重要。一个需要能够处理处理器的全部功能,而做到这一点则需要使用无符号数字。

对于.NET面向的应用程序种类,对全域无符号索引的需求不那么强烈。在托管语言中,此类数字的许多用途完全是无效的(想到了内存池)。此外,随着.NET的出现,64位计算机显然是未来。我们距离需要64位整数的完整范围还有很长的路要走,因此牺牲一位并不像以前那样痛苦。如果确实需要40亿个索引,则只需切换到使用64位整数即可。最糟糕的是,您在32位计算机上运行它,但速度有些慢。

我认为交易是一种便利。如果您碰巧拥有足够的计算能力,而又不介意浪费您永远不会使用的索引类型,那么键入intlong退出该索引将很方便。如果您发现确实想要最后一点,那么您可能应该注意数字的签名。


假设size()was 的实现return bar_ * baz_;;难道这现在不会产生整数溢出(环绕)的潜在问题,如果我不使用它,我将不会有这种问题size_t
Ðаn

5
@Dan您可以在无符号整数很重要的情况下构造类似的情况,在这种情况下,最好使用完整的语言功能来解决它​​。但是,我必须说,拥有一个bar_ * baz_可以溢出有符号整数而不是无符号整数的类是一个有趣的构造。限于C ++,值得注意的是规范中定义了无符号溢出,但是有符号溢出是未定义的行为,因此,如果需要使用无符号整数的模运算,则一定要使用它们,因为它是实际定义的!
Cort Ammon-恢复莫妮卡

1
@丹- 如果size()溢出的签署乘法,你在语言UB土地。(并且在fwrapv模式下,请参见下一个:)然后,仅用一点点的时间,它就会使无符号乘法溢出,您处于用户代码错误的境地-您将返回假的大小。因此,我认为未签名的人在这里买不到很多。
马丁·巴

4

我认为以上答案已经很好地突出了这些问题。

我将添加002:

  • size_t,即...

    可以存储任何类型(包括数组)的理论上可能存在的对象的最大大小。

    ...仅在时sizeof(type)==1(即您正在处理字节(char)类型)时才需要范围索引。(但是,我们注意到,它可以小于ptr类型

  • 这样,xxx::size_type即使它是带符号大小的类型,也可以在99.9%的情况下使用。(比较ssize_t
  • std::vector和朋友选择大小和索引size_t无符号类型的事实被某些人认为是设计缺陷。我同意。(认真地花了5分钟,观看了CppCon 2016闪电演讲:乔恩·卡尔布(Jon Kalb)“未签名:更好的代码指南”。)
  • 今天,当您设计C ++ API时,您将处于困境:用于size_t与标准库保持一致,或使用(带符号intptr_tssize_t进行容易且易于出错的索引计算。
  • 不要使用int32或int64- intptr_t如果要签名并希望使用机器字大小,请使用或使用ssize_t

要直接回答这个问题,它不完全是一个“历史文物”,因为需要解决一半以上(“索引”或“地址空间”)的理论问题必须以某种方式用低级语言解决,例如aehm C ++。

事后看来,我个人认为,这一个设计缺陷,即使标准库size_t不代表原始内存大小,而是代表集合(例如集合)的类型化数据的容量,它还是在整个地方使用无符号的:

  • 给定C ++的整数提升规则 ->
  • 对于诸如语义上无符号的大小之类的东西,无符号类型只是不适合“语义”类型。

我会在这里重复乔恩的建议

  • 选择它们支持的操作的类型(而不是值的范围)。(* 1)
  • 不要在您的API中使用无符号类型。隐藏了没有向上好处的错误。
  • 数量不要使用“无符号”。(* 2)

(* 1),即unsigned == bitmask,永远不要做数学运算(这里碰到第一个异常-您可能需要包装一个计数器-这必须是unsigned类型。)

(* 2)个数量表示您要计数和/或进行数学运算的数量。


“完全可用的平面内存”是什么意思?另外,确保您不希望将ssize_t定义为的签名吊坠,size_t而不是intptr_t,该吊坠可以存储任何(非成员)指针,因此可能更大?
Deduplicator

@Deduplicator-好吧,我想我可能已经把size_t定义弄乱了。请参见size_t与intptren.cppreference.com/w/cpp/types/size_t 。今天了解到一些新知识。:-)我认为其余的参数都成立了,我看看是否可以修复所使用的类型。
马丁·巴

0

出于性能原因,我将仅添加它,我通常使用size_t来确保计算错误会导致下溢,这意味着可以将范围检查(小于零和大于size())都减小为一:

使用带符号的int:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

使用unsigned int:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
真的想更彻底地解释这一点。
马丁·巴

为了使答案更有用,也许您可​​以描述整数数组的边界或偏移量比较(有符号和无符号)在来自各种编译器供应商的机器代码中的外观。有许多在线C ++编译器和反汇编站点可以显示给定C ++代码和编译器标志的相应已编译机器代码。
rwong

我试图对此进行更多解释。
asger 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.