ChatGPT解决这个技术问题 Extra ChatGPT

何时使用 std::size_t?

我只是想知道我应该使用 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
CB Bailey

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

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

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


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

size_tsizeof 运算符的结果类型。

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

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


p
paxdiablo

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

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; )

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


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

根据定义,size_tsizeof 运算符的结果。创建 size_t 是为了指代尺寸。

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

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

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


A
Arne

简短的回答:

几乎从不

长答案:

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

size_t 是一种非常易读的方式来指定项目的大小维度 - 字符串的长度、指针占用的字节数等。它还可以跨平台移植 - 您会发现 64 位和 32 位都可以很好地与系统配合使用函数和 size_t - unsigned int 可能不会做的事情(例如,您何时应该使用 unsigned long


P
Peter Alexander

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

对于 STL 容器,您将拥有(例如)vector<int>::size_type,它应该用于索引和计数向量元素。

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


对于 linux 上的 gcc,std::size_t 通常是 unsigned long(在 64 位系统上为 8 个字节)而不是 unisgned int(4 个字节)。
但是,C 风格的数组没有被 size_t 索引,因为索引可以是负数。但是,如果不想变成负数,可以将 size_t 用于自己的此类数组实例。
由于 C 风格的数组索引等同于在指针上使用运算符 +,因此似乎 ptrdiff_t 是用于索引的那个。
至于 vector<T>::size_type(对于所有其他容器也是如此),它实际上是相当无用的,因为它被有效地保证为 size_t - 它的 typedef'd 为 Allocator::size_type,关于容器的限制参见 20.1 .5/4 - 特别是,size_type 必须是 size_t,而 difference_type 必须是 ptrdiff_t。当然,默认的 std::allocator<T> 满足这些要求。所以只需使用较短的 size_t 并且不要打扰其余的部分 :)
我必须评论 C 风格的数组和负索引。是的,你可以,但你不应该。在数组边界之外访问是未定义的。如果你用指针做一些棘手的事情,用数组索引而不是指针数学(和大量代码注释)来做是一个令人困惑的坏主意。
K
KittMedia

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

为未来做准备!


您的论点仅意味着人们需要 long int 而不是 int。如果 size_t 与 64 位操作系统相关,则它与 32 位操作系统同样相关。
a
ascotan

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 而不用担心这个错误,我很难学会这一点
K
Kemin Zhou

使用 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 表达式中得到错误。我花了几天的时间才意识到这一点(代码太简单了,我没有做单元测试),尽管只需要几分钟就可以找出问题的根源。不确定进行强制转换或使用零会更好。

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

我想听听别人的意见。


请注意,(int)(i - 7) 是一个下溢,然后转换为 int,而 int(i) - 7 不是下溢,因为您首先将 i 转换为 int,然后减去 7。此外,我发现您的示例令人困惑。
我的观点是,当你做减法时, int 通常更安全。
Y
Yann

通常最好不要在循环中使用 size_t。例如,

vector<int> a = {1,2,3,4};
for (size_t i=0; i<a.size(); i++) {
    std::cout << a[i] << std::endl;
}
size_t n = a.size();
for (size_t i=n-1; i>=0; i--) {
    std::cout << a[i] << std::endl;
}

第一个循环没问题。但是对于第二个循环:当 i=0 时,i-- 的结果将是 ULLONG_MAX(假设 size_t = unsigned long long),这不是您在循环中想要的。此外,如果 a 为空,则 n=0 且 n-1=ULLONG_MAX 也不好。


U
Unknown

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

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

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


H
Hilario Nengare

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

它用于以字节为单位表示对象的大小,因此它被 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 <stddef.h> 也没有 #include <string.h>。相反,我们使用 #include <string>#include <cstddef>,并使用 std::string。 C 和 C++ 是不同的语言。
哎呀。抱歉真的没注意,谢谢
m
monkeyking

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

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

通过使用适当的有符号/无符号数据类型或简单地进行类型转换以进行快速破解来避免这种情况。


仅当您想避免错误和安全漏洞时才使用它。
它实际上可能无法表示系统上的最大整数。
A
Ashish

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 位无符号整数。