【问题标题】:When to use std::size_t?何时使用 std::size_t?
【发布时间】:2020-12-18 13:23:29
【问题描述】:

我只是想知道我应该使用std::size_t 来代替int 吗? 例如:

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

一般来说,关于何时使用std::size_t 的最佳做法是什么?

【问题讨论】:

    标签: c++ types idioms size-t loop-counter


    【解决方案1】:

    一个好的经验法则是,您需要在循环条件中与自然是 std::size_t 本身的事物进行比较。

    std::size_t 是任何sizeof 表达式的类型,并且保证能够表示C++ 中任何对象(包括任何数组)的最大大小。通过扩展,它还保证对于任何数组索引都足够大,因此它是数组索引循环的自然类型。

    如果您只是计算一个数字,那么使用包含该数字的变量类型或intunsigned int(如果足够大)可能更自然,因为这些应该是自然的机器尺寸。

    【讨论】:

    • 值得一提的是,使用size_t时应该可以导致security bugs
    • int 不仅是“自然的”,而且混合有符号和无符号类型也会导致安全漏洞。无符号索引很难处理,也是使用自定义向量类的好理由。
    • @JoSo 还有 ssize_t 用于签名值。
    • @EntangledLoops ssize_t 没有size_t 的完整范围。它只是 size_t 将转换为的任何签名变体。这意味着,内存的全部范围不适用于ssize_t,并且当依赖于size_t 类型的变量时,可能会发生整数溢出。
    • @Thomas 是的,但我不确定你在说什么。我的意思是作为int 的替代品,它更符合语义。您对ssize_t 不提供全部范围的评论是正确的,但int 也是如此。真正重要的是为应用程序使用适当的类型。
    【解决方案2】:

    size_tsizeof 运算符的结果类型。

    size_t 用于对数组中的大小或索引进行建模的变量。 size_t 传达了语义:您立即知道它表示以字节或索引为单位的大小,而不仅仅是另一个整数。

    此外,使用size_t 表示以字节为单位的大小有助于使代码具有可移植性。

    【讨论】:

      【解决方案3】:

      size_t 类型用于指定某事物的大小,因此使用它很自然,例如,获取字符串的长度,然后处理每个字符:

      for (size_t i = 0, max = strlen (str); i < max; i++)
          doSomethingWith (str[i]);
      

      确实当然必须注意边界条件,因为它是无符号类型。顶端的边界通常不是那么重要,因为最大值通常很大(尽管 有可能到达那里)。大多数人只是将int 用于此类事情,因为他们很少有结构或数组大到足以超过int 的容量。

      但要注意以下情况:

      for (size_t i = strlen (str) - 1; i >= 0; i--)
      

      由于无符号值的包装行为,这将导致无限循环(尽管我已经看到编译器对此提出警告)。这也可以通过(稍微难以理解但至少不受包装问题的影响)来缓解:

      for (size_t i = strlen (str); i-- > 0; )
      

      通过将减量转换为继续条件的检查后副作用,这会在减量之前检查值的继续,但仍使用循环内的减量值(其中这就是为什么循环从len .. 1而不是len-1 .. 0运行的原因。

      【讨论】:

      • 顺便说一句,在循环的每次迭代中调用strlen 是一种不好的做法。 :) 你可以这样做:for (size_t i = 0, len = strlen(str); i &lt; len; i++) ...
      • 即使它是有符号类型,您也必须注意边界条件,可能更是如此,因为有符号整数溢出是未定义的行为。
      • 正确倒计时可以通过以下(臭名昭著的)方式完成:for (size_t i = strlen (str); i --&gt; 0;)
      • @JoSo,虽然我不确定我是否喜欢 --&gt; "goes to" 运算符的引入(请参阅 stackoverflow.com/questions/1642028/…),但这实际上是一个非常巧妙的技巧。已将您的建议纳入答案。
      • 你能在 for 循环的末尾做一个简单的if (i == 0) break;吗(例如,for (size_t i = strlen(str) - 1; ; --i)。但我更喜欢你的,但只是想知道这是否也可以)。
      【解决方案4】:

      根据定义,size_tsizeof 运算符的结果。 size_t 是为了引用大小而创建的。

      您做某事的次数(在您的示例中为 10 次)与大小无关,那么为什么要使用 size_tint,或unsigned int,应该没问题。

      当然,你在循环中使用i 所做的事情也很重要。例如,如果您将其传递给采用unsigned int 的函数,则选择unsigned int

      无论如何,我建议避免隐式类型转换。 Make all type conversions explicit.

      【讨论】:

        【解决方案5】:

        简短回答:

        几乎没有

        长答案:

        每当您需要在 32 位系统上拥有大于 2gb 的 char 向量时。在所有其他用例中,使用有符号类型比使用无符号类型更安全。

        示例:

        std::vector<A> data;
        [...]
        // calculate the index that should be used;
        size_t i = calc_index(param1, param2);
        // doing calculations close to the underflow of an integer is already dangerous
        
        // do some bounds checking
        if( i - 1 < 0 ) {
            // always false, because 0-1 on unsigned creates an underflow
            return LEFT_BORDER;
        } else if( i >= data.size() - 1 ) {
            // if i already had an underflow, this becomes true
            return RIGHT_BORDER;
        }
        
        // now you have a bug that is very hard to track, because you never 
        // get an exception or anything anymore, to detect that you actually 
        // return the false border case.
        
        return calc_something(data[i-1], data[i], data[i+1]);
        

        size_t 的签名等效项是 ptrdiff_t,而不是 int。但在大多数情况下,使用int 仍然比 size_t 好得多。 ptrdiff_t 在 32 位和 64 位系统上是 long

        这意味着每当您与 std::containers 交互时,您总是必须在 size_t 之间进行转换,这不是很漂亮。但是在一个正在进行的本地会议上,c++ 的作者提到使用无符号 size_t 设计 std::vector 是一个错误。

        如果您的编译器在从 ptrdiff_t 到 size_t 的隐式转换时向您发出警告,您可以使用构造函数语法使其显式:

        calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);
        

        如果只是想迭代一个集合,没有边界检查,使用基于范围的:

        for(const auto& d : data) {
            [...]
        }
        

        这里是 Bjarne Stroustrup(C++ 作者)going native 的一些话

        对于某些人来说,STL 中的这种有符号/无符号设计错误是足够的理由,不使用 std::vector,而是使用自己的实现。

        【讨论】:

        • 我知道他们来自哪里,但我仍然认为写for(int i = 0; i &lt; get_size_of_stuff(); i++) 很奇怪。现在,当然,你可能不想做很多原始循环,但是 - 来吧,你也使用它们。
        • 我使用原始循环的唯一原因是因为 c++ 算法库设计得非常糟糕。有些语言,比如 Scala,有一个更好、更进化的库来操作集合。然后几乎消除了原始循环的用例。还有一些方法可以使用新的更好的 STL 来改进 c++,但我怀疑这会在未来十年内发生。
        • 我得到 unsigned i = 0;断言(i-1,MAX_INT);但我不明白你为什么说“如果我已经有下溢,这将成为真的”,因为总是定义无符号整数上的算术行为,即。结果是以最大可表示整数的大小取模的结果。因此,如果 i==0,则 i-- 变为 MAX_INT,然后 i++ 再次变为 0。
        • @mabraham 我仔细看了看,你是对的,我的代码不是最能说明问题的。通常这是x + 1 &lt; y 等价于x &lt; y - 1,但它们不是无符号整数。当事物被假定为等价时,这很容易引入错误。
        【解决方案6】:

        size_t 是一种非常易读的方式来指定项目的大小维度 - 字符串的长度、指针占用的字节数等。 它还可以跨平台移植——你会发现 64 位和 32 位都可以很好地处理系统功能和 size_t——这是 unsigned int 可能做不到的(例如,你什么时候应该使用 unsigned long

        【讨论】:

          【解决方案7】:

          使用 std::size_t 对 C 样式数组进行索引/计数。

          对于 STL 容器,您将拥有(例如)vector&lt;int&gt;::size_type,它应该用于索引和计数矢量元素。

          实际上,它们通常都是无符号整数,但不能保证,尤其是在使用自定义分配器时。

          【讨论】:

          • 在 linux 上使用 gcc,std::size_t 通常是 unsigned long(在 64 位系统上为 8 个字节)而不是 unisgned int(4 个字节)。
          • C 风格的数组没有被size_t 索引,因为索引可以是负数。不过,如果不想变成负数,可以将size_t 用于自己的此类数组实例。
          • 由于 C 样式的数组索引等同于在指针上使用运算符 +,因此似乎 ptrdiff_t 是用于索引的。
          • 至于vector&lt;T&gt;::size_type(对于所有其他容器也是如此),它实际上是相当无用的,因为它被有效地保证为size_t - 它的类型定义为Allocator::size_type,并且用于限制关于容器,请参阅 20.1.5/4 - 特别是,size_type 必须是 size_tdifference_type 必须是 ptrdiff_t。当然,默认的std::allocator&lt;T&gt; 满足这些要求。所以只需使用较短的size_t,不要打扰其余的部分:)
          • 我必须评论一下 C 风格的数组和负索引。是的,你可以,但你不应该。在数组边界之外访问是未定义的。如果你用指针做一些棘手的事情,用数组索引而不是指针数学(和很多代码 cmets)来做是一个令人困惑的坏主意。
          【解决方案8】:

          很快,大多数计算机将采用 64 位架构,配备 64 位操作系统:运行在包含数十亿个元素的容器上运行的程序。那么你必须使用size_t而不是int作为循环索引,否则你的索引将环绕在第 2^32:th 元素,在 32- 和64 位系统。

          为未来做好准备!

          【讨论】:

          • 你的论点仅仅意味着需要long int而不是int。如果size_t 与 64 位操作系统相关,则它与 32 位操作系统同样相关。
          【解决方案9】:

          size_t 由各种库返回,以指示该容器的大小非零。当你回来时使用它:0

          但是,在上面的示例中,在 size_t 上循环是一个潜在的错误。考虑以下几点:

          for (size_t i = thing.size(); i >= 0; --i) {
            // this will never terminate because size_t is a typedef for
            // unsigned int which can not be negative by definition
            // therefore i will always be >= 0
            printf("the never ending story. la la la la");
          }
          

          使用无符号整数有可能产生这些类型的微妙问题。因此恕我直言,我更喜欢仅在与需要它的容器/类型交互时才使用 size_t。

          【讨论】:

          • Everone 似乎在循环中使用 size_t 而不用担心这个错误,我很难学会这一点
          【解决方案10】:

          使用 size_t 时请注意以下表达式

          size_t i = containner.find("mytoken");
          size_t x = 99;
          if (i-x>-1 && i+x < containner.size()) {
              cout << containner[i-x] << " " << containner[i+x] << endl;
          }
          

          无论你对 x 有什么值,你都会在 if 表达式中得到 false。 我花了好几天才意识到这一点(代码太简单了,我没有做单元测试),尽管只花了几分钟就弄清楚了问题的根源。不确定是进行强制转换还是使用零更好。

          if ((int)(i-x) > -1 or (i-x) >= 0)
          

          这两种方法都应该有效。这是我的测试运行

          size_t i = 5;
          cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;
          

          输出:i-7=18446744073709551614 (int)(i-7)=-2

          我想要别人的cmets。

          【讨论】:

          • 请注意(int)(i - 7) 是一个下溢,之后会转换为int,而int(i) - 7 不是下溢,因为您首先将i 转换为int,然后减去7。此外,我发现您的示例令人困惑。
          • 我的意思是 int 通常在你做减法时更安全。
          【解决方案11】:

          size_t 是一种无符号类型,可以为您的体系结构保存最大整数值,因此可以防止由于符号(有符号 int 0x7FFFFFFF 增加 1 将给您 -1)或短尺寸(无符号short int 0xFFFF 加 1 会得到 0)。

          主要用于数组索引/循环/地址运算等。 memset() 之类的函数只接受 size_t,因为理论上您可能有一块大小为 2^32-1 的内存块(在 32 位平台上)。

          对于这样简单的循环,不要打扰,只需使用 int。

          【讨论】:

            【解决方案12】:

            我一直在努力理解什么以及何时使用它。但是 size_t 只是一个无符号整数数据类型,它在各种头文件中定义,例如 &lt;stddef.h&gt;, &lt;stdio.h&gt;, &lt;stdlib.h&gt;, &lt;string.h&gt;, &lt;time.h&gt;, &lt;wchar.h&gt; 等。

            它用于以字节为单位表示对象的大小,因此它被 sizeof 运算符用作返回类型。最大允许大小取决于编译器;如果编译器是 32 位,那么它只是 unsigned int 的 typedef(别名),但如果编译器是 64 位,那么它将是 unsigned long long 的 typedef。 size_t 数据类型永远不会是负数(不包括 ssize_t) 因此,许多 C 库函数(如 malloc, memcpy and strlen)将其参数和返回类型声明为 size_t

            / Declaration of various standard library functions.
              
            // Here argument of 'n' refers to maximum blocks that can be
            // allocated which is guaranteed to be non-negative.
            void *malloc(size_t n);
              
            // While copying 'n' bytes from 's2' to 's1'
            // n must be non-negative integer.
            void *memcpy(void *s1, void const *s2, size_t n);
              
            // the size of any string or `std::vector<char> st;` will always be at least 0.
            size_t strlen(char const *s);
            

            size_t 或任何无符号类型都可能被视为循环变量,因为循环变量通常大于或等于 0。

            【讨论】:

            • 你的答案都是关于 C 语言的,但是问题被标记为 C++。在 C++ 中,我们不使用 malloc/free,即使 new/delete 在 C++ 中也很少有有效的用例。对于动态内存管理,我们使用智能指针(例如std::unique_ptr)代替(如果甚至需要,因为通常可以使用标准容器来完成常规操作,例如std::vector)。此外,在 C++ 中,我们不使用 #include &lt;stddef.h&gt;#include &lt;string.h&gt;。相反,我们使用#include &lt;string&gt;#include &lt;cstddef&gt;,并使用std::string。 C 和 C++ 是不同的语言。
            • 糟糕。抱歉真的没注意,谢谢
            【解决方案13】:

            size_t 是无符号整数类型,可以表示系统中的最大整数。 只有在需要非常大的数组、矩阵等时才使用它。

            有些函数会返回 size_t,如果您尝试进行比较,编译器会警告您。

            通过使用适当的有符号/无符号数据类型或简单的类型转换来避免这种情况。

            【讨论】:

            • 只有在你想避免错误和安全漏洞时才使用它。
            • 它实际上可能无法表示您系统上的最大整数。
            【解决方案14】:

            size_t 是无符号整数。所以只要你想要 unsigned int 就可以使用它。

            当我想指定数组的大小时使用它,计数器等...

            void * operator new (size_t size); is a good use of it.
            

            【讨论】:

            • 其实不一定和unsigned int一样。它无符号的,但它可能比 int 更大(或者我猜想更小,虽然我不知道有哪个平台是这样的)。
            • 例如,在 64 位机器上 size_t 可能是一个无符号 64 位整数,而在 32 位机器上它只是一个 32 位无符号整数。
            猜你喜欢
            • 1970-01-01
            • 2016-12-05
            • 2014-10-17
            • 2020-05-06
            • 2011-10-31
            • 2017-12-23
            • 2018-02-15
            相关资源
            最近更新 更多