ChatGPT解决这个技术问题 Extra ChatGPT

C中的size_t是什么?

我对 C 中的 size_t 感到困惑。我知道它是由 sizeof 运算符返回的。但它到底是什么?它是一种数据类型吗?

假设我有一个 for 循环:

for(i = 0; i < some_size; i++)

我应该使用 int i; 还是 size_t i;

如果这些是您唯一的选择,如果 some_size 已签名,则使用 int,如果未签名,则使用 size_t
@Nate 这是不正确的。 POSIX 有一个 ssize_t 类型,但实际使用的正确类型是 ptrdiff_t。
答案不如 Low-Level Programming: C, Assembly, and Program Execution on Intel® 64 中的那么清楚。如书中所述,使用索引 int i 可能不足以处理一个巨大的数组。因此,通过使用 size_t i 您可以处理更多索引,因此即使您有一个庞大的数组也不应该成为问题。 size_t 是一种数据类型:通常是 unsigned long int,但这取决于您的系统。

C
Community

From Wikipedia

根据 1999 ISO C 标准 (C99),size_t 是至少 16 位的无符号整数类型(参见第 7.17 和 7.18.3 节)。 size_是由几个 C/C++ 标准定义的无符号数据类型,例如 C99 ISO/IEC 9899 标准,在 stddef.h.1 中定义。它可以通过包含 stdlib.h 进一步导入,因为该文件内部子包含 stddef。 H。此类型用于表示对象的大小。采用或返回大小的库函数期望它们是类型或返回类型为 size_t。此外,最常用的基于编译器的运算符 sizeof 应计算为与 size_t 兼容的常量值。

作为暗示,size_t 是一种保证保存任何数组索引的类型。


“采用或返回大小的库函数期望它们的类型为 ... size_t”除了 stat() 使用 off_t 来表示文件的大小
@Draemon该评论反映了一种根本的困惑。 size_t 用于内存中的对象。 C 标准甚至没有定义 stat()off_t(这些是 POSIX 定义)或与磁盘或文件系统有关的任何内容 - 它在 FILE 流处停止自身。就大小要求而言,虚拟内存管理与文件系统和文件管理完全不同,因此此处提及 off_t 无关紧要。
@jw013:我很难称其为根本性混乱,但您提出了一个有趣的观点。尽管如此,引用的文本并没有说“内存中对象的大小”,并且“偏移”对于大小类型来说几乎不是一个好名字,无论它碰巧存储在哪里。
@Draemon 好点。这个答案引用了维基百科,在我看来,在这种情况下它没有最好的解释。 C 标准本身要清楚得多:它将 size_t 定义为 sizeof 运算符的结果类型(关于 <stddef.h> 的 7.17p2)。 6.5 节准确解释了 C 表达式的工作原理(6.5.3.4 用于 sizeof)。由于您不能将 sizeof 应用于磁盘文件(主要是因为 C 甚至没有定义磁盘和文件的工作方式),因此没有混淆的余地。换句话说,责怪维基百科(以及这个引用维基百科而不是实际的 C 标准的答案)。
@Draemon - 我也同意“基本混乱”的评估。如果您没有阅读过 C/C++ 标准,您可能会认为“对象”指的是“面向对象编程”,但实际上并非如此。阅读 C 标准,它没有任何 OOP 对象,但有对象,然后找出答案。答案可能会让你大吃一惊!
G
Gaurang Tandon

size_t 是无符号类型。因此,它不能表示任何负值(<0)。你在计算某物时使用它,并确定它不能为负数。例如,strlen() 返回 size_t,因为字符串的长度必须至少为 0。

在您的示例中,如果您的循环索引将始终大于 0,则使用 size_t 或任何其他无符号数据类型可能是有意义的。

当您使用 size_t 对象时,您必须确保在使用它的所有上下文(包括算术)中,您需要非负值。例如,假设您有:

size_t s1 = strlen(str1);
size_t s2 = strlen(str2);

并且您想找出 str2str1 长度的差异。你不能这样做:

int diff = s2 - s1; /* bad */

这是因为分配给 diff 的值总是一个正数,即使是 s2 < s1,因为计算是使用无符号类型完成的。在这种情况下,根据您的用例,您最好将 int(或 long long)用于 s1s2

C/POSIX 中有一些函数可以/应该使用 size_t,但由于历史原因不能使用。例如,fgets 的第二个参数理想情况下应该是 size_t,但实际上是 int


@Alok:两个问题:1)size_t 的大小是多少? 2) 为什么我更喜欢 size_t 而不是 unsigned int
@Lazer:size_t 的大小是 sizeof(size_t)。 C 标准保证 SIZE_MAX 至少为 65535。size_tsizeof 运算符返回的类型,在标准库中使用(例如 strlen 返回 size_t)。正如 Brendan 所说,size_t 不必与 unsigned int 相同。
@Lazer - 是的,size_t 保证是无符号类型。
请记住,在 64 位 Linux 上,int 始终是 32 位,但 size_t 是 64 位。所以 size_t 和 int 是不可互换的。
@JasonOster,二进制补码不是 C 标准中的要求。如果 s2 - s1 的值溢出 int,则行为未定义。
A
Arjun Sreedharan

size_t 是一种可以保存任何数组索引的类型。

根据实现,它可以是以下任何一种:

unsigned char

unsigned short

unsigned int

unsigned long

unsigned long long

以下是在我的机器的 stddef.h 中定义 size_t 的方式:

typedef unsigned long size_t;

@chux:确实,仅仅因为一种实现将其定义为这样并不意味着所有人都这样做。例如:64 位 Windows。 unsigned long 是 32 位,size_t 是 64 位。
size_t 在 32 位机器上总是 32 位,同样是 64 位,这是真的吗?
“根据 1999 ISO C 标准 (C99),size_t 是至少 16 位的无符号整数类型(参见第 7.17 和 7.18.3 节)。”所以它不能是unsigned char
@jameshfisher 我不确定 16 位限制是否属实。 uint_least16_t 是至少 16 位。关于,size_t,标准说“sizeof 运算符结果的无符号整数类型”和“sizeof 运算符产生其操作数的大小(以字节为单位)”。
@jameshfisher 说 unsigned char 不能是 16 位?!
C
Community

如果你是经验型,

echo | gcc -E -xc -include 'stddef.h' - | grep size_t

Ubuntu 14.04 64 位 GCC 4.8 的输出:

typedef long unsigned int size_t;

请注意,stddef.h 由 GCC 提供,而不是 GCC 4.2 中 src/gcc/ginclude/stddef.h 下的 glibc。

有趣的 C99 出场

malloc 将 size_t 作为参数,因此它确定可以分配的最大大小。而且由于它也是由 sizeof 返回的,我认为它限制了任何数组的最大大小。另请参阅:C 中数组的最大大小是多少?


我有相同的环境,但是,我已经对它进行了 32 位测试,通过了 GCC 的“-m32”选项,结果是:“typedef unsigned int size_t”。感谢分享这个很棒的命令@Ciro,它对我帮助很大! :-)
事情本身并不令人困惑。试图提出许多问题并给出许多答案的是混乱的头脑。我很惊讶这个答案和 Arjun Sreedharan 的答案仍然没有阻止人们提问和回答。
c
codaddict

types.h 的联机帮助页说:

size_t 应为无符号整数类型


P
Peter Mortensen

要了解为什么需要存在 size_t 以及我们是如何到达这里的:

在实用方面,size_tptrdiff_t 在 64 位实现上保证为 64 位宽,在 32 位实现上保证为 32 位宽,依此类推。他们不能在不破坏遗留代码的情况下,在每个编译器上强制任何现有类型都意味着这一点。

size_tptrdiff_t 不一定与 intptr_tuintptr_t 相同。它们在某些体系结构上有所不同,这些体系结构在 1980 年代后期将 size_tptrdiff_t 添加到标准时仍在使用,而当 C99 添加了许多新类型但尚未消失(例如 16 位视窗)。 16 位保护模式下的 x86 有一个分段内存,其中最大可能的数组或结构可能只有 65,536 字节大小,但 far 指针需要 32 位宽,比寄存器宽。在那些上,intptr_t 将是 32 位宽,但 size_tptrdiff_t 可能是 16 位宽并适合寄存器。谁知道将来会写出什么样的操作系统?理论上,i386 架构提供了一个 32 位分段模型和 48 位指针,没有操作系统实际使用过。

内存偏移的类型不能是 long,因为太多的旧代码假定 long 正好是 32 位宽。这一假设甚至内置于 UNIX 和 Windows API。不幸的是,许多其他遗留代码也假设 long 的宽度足以容纳指针、文件偏移量、自 1970 年以来经过的秒数等等。 POSIX 现在提供了一种标准化的方法来强制后者假设为真,而不是前者,但两者都不是一个可移植的假设。

它不可能是 int,因为在 90 年代只有极少数编译器将 int 设为 64 位宽。然后通过保持 long 32 位宽,他们真的变得很奇怪。该标准的下一个修订版宣布 intlong 宽是非法的,但在大多数 64 位系统上 int 仍然是 32 位宽。

它不可能是 long long int,它是后来添加的,因为即使在 32 位系统上,它也被创建为至少 64 位宽。

因此,需要一种新类型。即使不是这样,所有其他类型也意味着数组或对象中的偏移量以外的东西。如果从 32 位到 64 位迁移的惨败中有一个教训,那就是具体说明一种类型需要具有哪些属性,而不是使用在不同程序中意味着不同事物的属性。


不同意“size_tptrdiff_t 在 64 位实现上保证为 64 位宽”等。保证被夸大了。 size_t 的范围主要由实现的内存容量决定。 “n 位实现”主要是整数的本机处理器宽度。当然,许多实现使用类似大小的内存和处理器总线宽度,但是存在内存不足的宽本机整数或内存很多的窄处理器,并且确实将这两个实现属性分开。
不。说“n 位实现主要是整数的本机处理器宽度”是完全错误的。 C编译器上下文中的n位实现是指操作系统上指针的宽度(或更准确地说,是软件打算运行的操作系统当前架构模式下的指针宽度,例如为具有 32 位兼容模式的 64 位操作系统编译 32 位应用程序的情况),无论硬件如何。
支持 64 位的硬件已经存在了很长时间,但有些情况仍然需要 a) 运行为操作系统的 32 位兼容模式(也就是 64 位操作系统上的 32 位应用程序)编译的代码,甚至b) 恢复到 32 位操作系统。这并不少见,因为许多遗留应用程序尚未彻底重构和重新测试,以确保指针操作永远不会做出任何 32 位假设,从而截断指针的高 32 位(以前很常见,但现在借助 size_t 和 uintptr_t 等工具,完全可以避免。)。
s
supercat

由于尚未有人提及它,因此 size_t 的主要语言意义是 sizeof 运算符返回该类型的值。同样,ptrdiff_t 的主要意义是从另一个指针中减去一个指针将产生该类型的值。接受它的库函数这样做是因为它允许此类函数在可能存在此类对象的系统上处理大小超过 UINT_MAX 的对象,而不会强制调用者浪费代码在较大类型的系统上传递大于“unsigned int”的值对于所有可能的对象就足够了。


我的问题一直是:如果 sizeof 从未存在,是否需要 size_t?
@DeanP:也许不是,但会有一个问题是应该使用什么参数类型来处理 malloc() 之类的事情。就个人而言,我希望看到采用 intlonglong long 类型的参数的版本,其中一些实现促进更短的类型,而其他实现例如 lmalloc(long n) {return (n < 0 || n > 32767) ? 0 : imalloc(n);} [在某些平台上,调用 {6 } 会比调用 lmalloc(123); 便宜,甚至在 size_t 为 16 位的平台上,想要分配以“long”值计算的大小的代码......
...如果值大于分配器可以处理的值,则应该能够依赖分配失败。
d
dtoux

size_tint 不可互换。例如,在 64 位 Linux 上,size_t 的大小是 64 位(即 sizeof(void*)),但 int 是 32 位。

另请注意,size_t 未签名。如果您需要签名版本,则某些平台上有 ssize_t,它与您的示例更相关。

作为一般规则,我建议在大多数一般情况下使用 int,并且仅在有特定需要时才使用 size_t/ssize_t(例如使用 mmap())。


P
Peter Mortensen

size_t 是一种无符号整数数据类型,只能分配 0 和大于 0 的整数值。它测量任何对象大小的字节,并由 sizeof 运算符返回。

constsize_t 的语法表示,但没有 const 您可以运行程序。

const size_t number;

size_t 经常用于数组索引和循环计数。如果编译器是 32-bit,它将在 unsigned int 上运行。如果编译器是 64-bit,它也可以在 unsigned long long int 上工作。 size_t 的最大大小取决于编译器类型。

size_t 已在 <stdio.h> 头文件中定义,但也可以由 <stddef.h><stdlib.h><string.h><time.h><wchar.h> 头文件定义。

示例(使用 const)

#include <stdio.h>

int main()
{
    const size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0 ; i < value ; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

输出: size = 800

示例(无 const)

#include <stdio.h>

int main()
{
    size_t value = 200;
    size_t i;
    int arr[value];

    for (i = 0; i < value; ++i)
    {
        arr[i] = i;
    }

    size_t size = sizeof(arr);
    printf("size = %zu\n", size);
}

输出: size = 800


P
Prince

size_t 是无符号整数数据类型。在使用 GNU C 库的系统上,这将是 unsigned int 或 unsigned long int。 size_t 通常用于数组索引和循环计数。


P
Peter Mortensen

一般来说,如果您从 0 开始向上,请始终使用无符号类型以避免溢出导致您陷入负值情况。这一点非常重要,因为如果您的数组边界恰好小于循环的最大值,但您的循环最大值恰好大于您的类型的最大值,您将环绕负数并且您可能会遇到 segmentation fault ( SIGSEGV)。所以,一般来说,永远不要将 int 用于从 0 开始并向上的循环。使用无符号。


我不能接受你的论点。您说溢出错误最好静默地导致访问数组中的有效数据?
@maf-soft 是正确的。如果错误未被发现,它比程序崩溃更糟糕。为什么这个答案得到了赞成?
如果它访问数组中的有效数据,那么这不是错误,因为无符号类型不会在有符号类型的极限处溢出。各位大佬这是什么逻辑?假设由于某种原因,您使用 char 迭代 256 个元素数组...有符号将在 127 处溢出,第 128 个元素将 sigsegv,但如果您使用无符号,那么它将按预期遍历整个数组。再说一次,当您使用 int 时,您的数组实际上不会大于 20 亿个元素,所以无论哪种方式都无关紧要......
我无法想象整数溢出不是错误的任何情况,无论它环绕正数还是负数。仅仅因为您没有收到段错误并不意味着您看到了正确的行为!无论您的偏移量是正数还是负数,您都可能遇到分段错误;这完全取决于您的内存布局。 @PurpleIce,我认为您所说的与此答案不同;您的论点似乎是您应该选择一个足够大的数据类型以容纳您想要放入其中的最大值,这只是常识。
也就是说,我确实更喜欢在语义上对循环索引使用无符号类型;如果您的变量永远不会是负数,那么您不妨在您选择的类型中指出这一点。它还可以让编译器发现值最终为负数的错误,尽管 GCC 至少在发现这个特定错误方面非常糟糕(有一次我将 unsigned 初始化为 -1 并且没有收到警告)。同样, size_t 在语义上适用于数组索引。
l
l3x

size_t 是一个 typedef,用于表示任何对象的大小(以字节为单位)。 (Typedef 用于为另一种数据类型创建附加名称/别名,但不会创建新类型。)

发现它在 stddef.h 中定义如下:

typedef unsigned long long size_t;

size_t 也在 <stdio.h> 中定义。

size_t 用作 sizeof 运算符的返回类型。

使用 size_t 和 sizeof 来定义数组大小参数的数据类型,如下所示:

#include <stdio.h>

void disp_ary(int *ary, size_t ary_size)
{
    for (int i = 0; i < ary_size; i++)
    {
        printf("%d ", ary[i]);
    }
}
 
int main(void)
{
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    int ary_size = sizeof(arr)/sizeof(int);
    disp_ary(arr, ary_size);
    return 0;
}

size_t 保证足够大以包含主机系统可以处理的最大对象的大小。

请注意,数组的大小限制实际上是编译和执行此代码的系统堆栈大小限制的一个因素。您应该能够在链接时调整堆栈大小(请参阅 ld 命令的 --stack-size 参数)。

为了让您大致了解堆栈大小:

嵌入式设备上的 4K

Win10 1M

在 Linux 上为 740 万

mallocmemcpystrlen 等许多 C 库函数将其参数和返回类型声明为 size_t

size_t 使程序员能够处理不同的类型,方法是添加/减去所需的元素数量,而不是使用以字节为单位的偏移量。

让我们通过检查它在 C 字符串和整数数组的指针算术运算中的用法来更深入地了解 size_t 可以为我们做什么:

下面是一个使用 C 字符串的示例:

const char* reverse(char *orig)
{
  size_t len = strlen(orig);
  char *rev = orig + len - 1;
  while (rev >= orig)
  {
    printf("%c", *rev);
    rev = rev - 1;  // <= See below
  }
  return rev;
}

int main() {
  char *string = "123";
  printf("%c", reverse(string));
}
// Output: 321

0x7ff626939004 "123"  // <= orig
0x7ff626939006 "3"    // <= rev - 1 of 3
0x7ff626939005 "23"   // <= rev - 2 of 3
0x7ff626939004 "123"  // <= rev - 3 of 3
0x7ff6aade9003 ""     // <= rev is indeterminant. This can be exploited as an out of bounds bug to read memory contents that this program has no business reading.

这对于理解使用 size_t 的好处不是很有帮助,因为无论您的体系结构如何,一个字符都是一个字节。

当我们处理数值类型时,size_t 变得非常有用。

size_t 类型就像一个整数,具有可以保存物理内存地址的好处;该地址根据执行它的平台类型更改其大小。

以下是我们在传递整数数组时如何利用 sizeof 和 size_t 的方法:

void print_reverse(int *orig, size_t ary_size)
{
  int *rev = orig + ary_size - 1;
  while (rev >= orig)
  {
    printf("%i", *rev);
    rev = rev - 1;
  }
}

int main()
{
  int nums[] = {1, 2, 3};
  print_reverse(nums, sizeof(nums)/sizeof(*nums));

  return 0;
}

0x617d3ffb44 1  // <= orig
0x617d3ffb4c 3  // <= rev - 1 of 3
0x617d3ffb48 2  // <= rev - 2 of 3
0x617d3ffb44 1  // <= rev - 3 of 3

上面,我们看到一个 int 占用 4 个字节(因为每个字节有 8 位,所以一个 int 占用 32 位)。

如果我们要创建一个 long 数组,我们会发现 long 在 linux64 操作系统上占用 64 位,但只有 32 bits on a Win64 system。因此,使用 t_size 将节省大量编码和潜在错误,尤其是在运行在不同架构上执行地址算术的 C 代码时。

所以这个故事的寓意是“使用 size_t 并让你的 C 编译器完成指针运算中容易出错的工作”。


b
bishwas pokharel

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

当我们使用 size_t 对象时,我们必须确保在所有使用它的上下文中,包括算术,我们只需要非负值。例如,下面的程序肯定会给出意想不到的结果:

// C program to demonstrate that size_t or
// any unsigned int type should be used 
// carefully when used in a loop

#include<stdio.h>
int main()
{
const size_t N = 10;
int a[N];

// This is fine
for (size_t n = 0; n < N; ++n)
a[n] = n;

// But reverse cycles are tricky for unsigned 
// types as can lead to infinite loop
for (size_t n = N-1; n >= 0; --n)
printf("%d ", a[n]);
}

Output
Infinite loop and then segmentation fault

A
Artem

这是特定于平台的 typedef。例如,在特定机器上,它可能是 unsigned intunsigned long。您应该使用此定义来提高代码的可移植性。


n
newfurniturey

据我了解,size_t 是一个 unsigned 整数,其位大小足以容纳本机架构的指针。

所以:

sizeof(size_t) >= sizeof(void*)

不对。指针大小可以大于 size_t。几个例子:x86 实模式下的 C 编译器可以有 32 位 FARHUGE 指针,但 size_t 仍然是 16 位。另一个例子:Watcom C 曾经有一个特殊的胖指针,用于 48 位宽的扩展内存,但 size_t 没有。在哈佛架构的嵌入式控制器上,您也没有相关性,因为两者都涉及不同的地址空间。
在那个 stackoverflow.com/questions/1572099/… 上有更多的示例 AS/400 具有 128 位指针和 32 位 size_t
这是公然错误的。但是,让我们把它留在这里