我和c中的size_t搞混了,我知道它是由sizeof操作符返回的。但它究竟是什么?它是数据类型吗?

假设我有一个for循环:

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

我应该用int I吗?或者size_t i;?


当前回答

Size_t是一个类型定义,用于以字节为单位表示任何对象的大小。(Typedefs用于为另一个数据类型创建额外的名称/别名,但不创建新类型。)

在stddef.h中找到它的定义如下:

typedef unsigned long long size_t;

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

Size_t被sizeof操作符用作返回类型。

使用size_t结合sizeof定义数组size参数的数据类型,如下所示:

#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 7.4M (Linux)

许多C库函数,如malloc, memcpy和strlen,声明它们的参数并返回size_t类型。

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类型类似于一个整数,它的好处是可以保存物理内存地址;该地址根据其执行的平台类型改变其大小。

下面是在传递int类型数组时如何利用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位,而在Win64系统上只需要32位。因此,使用t_size将节省大量的编码和潜在的错误,特别是在运行在不同体系结构上执行地址算术的C代码时。

因此,这个故事的寓意是“使用size_t,让c编译器来完成容易出错的指针算术工作。”

其他回答

Since nobody has yet mentioned it, the primary linguistic significance of size_t is that the sizeof operator returns a value of that type. Likewise, the primary significance of ptrdiff_t is that subtracting one pointer from another will yield a value of that type. Library functions that accept it do so because it will allow such functions to work with objects whose size exceeds UINT_MAX on systems where such objects could exist, without forcing callers to waste code passing a value larger than "unsigned int" on systems where the larger type would suffice for all possible objects.

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

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

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

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

你想求出str2和str1的长度之差。你不能:

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

这是因为赋给diff的值总是一个正数,即使s2 < s1,因为计算是用无符号类型完成的。在这种情况下,根据您的用例,对于s1和s2使用int(或long long)可能会更好。

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

Size_t是一种可以保存任何数组下标的类型。

根据实现的不同,它可以是:

它没有查尔

无符号短

无符号整型

无符号长

未签名的long long

下面是在我的机器的stddef.h中如何定义size_t:

typedef unsigned long size_t;

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

这是一个平台特定的类型定义。例如,在特定的机器上,它可能是unsigned int或unsigned long。您应该使用这个定义来提高代码的可移植性。