我和c中的size_t搞混了,我知道它是由sizeof操作符返回的。但它究竟是什么?它是数据类型吗?
假设我有一个for循环:
for(i = 0; i < some_size; i++)
我应该用int I吗?或者size_t i;?
我和c中的size_t搞混了,我知道它是由sizeof操作符返回的。但它究竟是什么?它是数据类型吗?
假设我有一个for循环:
for(i = 0; i < some_size; i++)
我应该用int I吗?或者size_t i;?
从维基百科:
According to the 1999 ISO C standard (C99), size_t is an unsigned integer type of at least 16 bit (see sections 7.17 and 7.18.3). size_tis an unsigned data type defined by several C/C++ standards, e.g. the C99 ISO/IEC 9899 standard, that is defined in stddef.h.1 It can be further imported by inclusion of stdlib.h as this file internally sub includes stddef.h. This type is used to represent the size of an object. Library functions that take or return sizes expect them to be of type or have the return type of size_t. Further, the most frequently used compiler-based operator sizeof should evaluate to a constant value that is compatible with size_t.
作为暗示,size_t是保证保存任何数组下标的类型。
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和int是不可互换的。例如,在64位Linux上size_t的大小是64位的(即sizeof(void*)),而int是32位的。
还要注意size_t是无符号的。如果你需要签名版本,那么在某些平台上有ssize_t,它与你的例子更相关。
作为一般规则,我建议在大多数情况下使用int,只有在特定需要时才使用size_t/ssize_t(例如mmap())。
一般来说,如果从0开始向上,总是使用无符号类型,以避免溢出将您带入负值的情况。这是非常重要的,因为如果你的数组边界刚好小于你的循环的最大值,但你的循环最大值刚好大于你的类型的最大值,你将绕负,你可能会遇到分割错误(SIGSEGV)。所以,一般来说,对于从0开始并向上的循环,永远不要使用int。使用unsigned符号。
Size_t是一种可以保存任何数组下标的类型。
根据实现的不同,它可以是:
它没有查尔
无符号短
无符号整型
无符号长
未签名的long long
下面是在我的机器的stddef.h中如何定义size_t:
typedef unsigned long size_t;
如果你是经验主义者,
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语言中数组的最大大小是多少?
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是无符号整数数据类型。在使用GNU C库的系统上,这将是unsigned int或unsigned long int。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
要了解为什么size_t需要存在,以及我们是如何到达这里的:
在实用术语中,size_t和ptrdiff_t在64位实现中保证为64位宽,在32位实现中保证为32位宽,等等。他们不能在不破坏遗留代码的情况下,在每个编译器上强制任何现有类型意味着这一点。
A size_t or ptrdiff_t is not necessarily the same as an intptr_t or uintptr_t. They were different on certain architectures that were still in use when size_t and ptrdiff_t were added to the Standard in the late 1980s, and becoming obsolete when C99 added many new types but not gone yet (such as 16-bit Windows). The x86 in 16-bit protected mode had a segmented memory where the largest possible array or structure could be only 65,536 bytes in size, but a far pointer needed to be 32 bits wide, wider than the registers. On those, intptr_t would have been 32 bits wide but size_t and ptrdiff_t could be 16 bits wide and fit in a register. And who knew what kind of operating system might be written in the future? In theory, the i386 architecture offers a 32-bit segmentation model with 48-bit pointers that no operating system has ever actually used.
The type of a memory offset could not be long because far too much legacy code assumes that long is exactly 32 bits wide. This assumption was even built into the UNIX and Windows APIs. Unfortunately, a lot of other legacy code also assumed that a long is wide enough to hold a pointer, a file offset, the number of seconds that have elapsed since 1970, and so on. POSIX now provides a standardized way to force the latter assumption to be true instead of the former, but neither is a portable assumption to make.
它不可能是int型,因为在90年代只有极少数的编译器将int型设置为64位宽。然后,他们真的很奇怪,保持长32位宽。标准的下一个修订版本宣布int比long更宽是非法的,但在大多数64位系统上int仍然是32位宽。
它不能是long long int,这是后来添加的,因为即使在32位系统上,它也至少被创建为64位宽。
因此,需要一种新的类型。即使不是,所有这些其他类型也意味着数组或对象内的偏移量以外的东西。如果说从32位到64位迁移的惨败中有什么教训的话,那就是要明确类型需要具有哪些属性,而不是在不同的程序中使用意味着不同内容的属性。
Size_t是一种无符号整型数据类型,它只能赋0和大于0的整型值。它测量任何对象大小的字节,并由sizeof操作符返回。
Const是size_t的语法表示,但是没有Const你也可以运行程序。
const size_t number;
Size_t经常用于数组索引和循环计数。如果编译器是32位的,它将在unsigned int上工作。如果编译器是64位的,它也可以在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
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编译器来完成容易出错的指针算术工作。”