注:并没有明确规定任何对象的大小不能超过SIZE_MAX
字节。你无法对这样的对象使用有效的sizeof
运算符,但就像任何其他运算符一样,sizeof
也可能溢出;这并不意味着你不能对这样的对象执行操作。但实际上,任何理智的实现都会将size_t
设得足够大,以表示其支持的任何对象的大小。
far
指针,但我的理解是它们包括段号和段内偏移量。如果16位指针可以访问20位地址空间,那么必须至少有4位隐式信息在其他地方,而据我所知,这正是far
指针不做的事情。在符合C实现的情况下,void*
可以唯一地寻址当前存在的每个对象的每个字节。 - Keith Thompsonstatic
关键字,因此它具有自动存储期。在大多数实现中,这些对象都分配在堆栈上。很可能你的系统限制了堆栈的大小。使用static
,或者全局定义,或者使用malloc()
。 - Keith ThompsonSIZE_MAX
字节大小的对象,但实际上,任何能够支持大于2**32字节的对象的合理实现都会使size_t
比32位更宽。一个允许你定义这样大对象但不费心扩展size_t
以容纳它们大小的实现可能是符合标准的,但这是反常的。 - Keith ThompsonC99 5.2.4.1 "翻译限制" 最小尺寸
实现必须能够翻译和执行至少包含以下每个限制中的至少一个实例的程序:13)
- 对象中的65535字节(仅在托管环境中)
- 尽可能避免强制实施固定的翻译限制。
这表明符合规范的实现可以拒绝编译具有超过short
字节的对象(包括数组)。
PTRDIFF_MAX
也对数组施加了一些限制
C99标准6.5.6加法运算符说:
当两个指针相减时,它们都必须指向同一数组对象的元素或该数组对象的最后一个元素之后;结果是两个数组元素下标的差。结果的大小是实现定义的,并且其类型(有符号整数类型)在<stddef.h>
头文件中定义为ptrdiff_t
。如果结果不能用该类型的对象表示,则行为未定义。ptrdiff_t
更大的数组,但是您无法以可移植的方式获取它们的地址差异。ptrdiff_t
。这也在Why is the maximum size of an array "too large"?中提到。main.c
#include <stdint.h>
TYPE a[(NELEMS)];
int main(void) {
return 0;
}
sizes.c
#include <stdint.h>
#include <stdio.h>
int main(void) {
printf("PTRDIFF_MAX 0x%jx\n", (uintmax_t)PTRDIFF_MAX);
printf("SIZE_MAX 0x%jx\n", (uintmax_t)SIZE_MAX);
return 0;
}
然后我们尝试使用以下命令进行编译:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o sizes.out sizes.c
./sizes.out
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out \
-DNELEMS='((2lu << 62) - 1)' -DTYPE=uint8_t main.c
结果:
PTRDIFF_MAX: 0x7fffffffffffffff = 2^63 - 1
SIZE_MAX: 0xffffffffffffffff = 2^64 - 1
-DNELEMS='((2lu << 62) - 1)' -DTYPE=uint8_t
: 编译成功(等于2^63-1)。在我的仅有32GB RAM的系统上运行它会立即崩溃 :-)
-DNELEMS='(2lu << 62)' -DTYPE=uint8_t
: 编译失败,报错:
error: size of array ‘a’ is too large
-DNELEMS='(2lu << 62 - 1)' -DTYPE=uint16_t
: 编译失败,报错:
error: size ‘18446744073709551614’ of array ‘a’ exceeds maximum object size ‘9223372036854775807’
其中 9223372036854775807 == 0x7fffffffffffffff
这样我们就可以理解GCC施加了两个不同错误信息的限制:
在Ubuntu 20.04 amd64,GCC 9.3.0上测试通过。
另请参阅
-m32
进行测试,得到了-536870896
(指针减法)与536870928
(手动计算)的结果。在x86上的32位用户空间中(在64位内核下),Glibc的malloc
成功分配了2GiB + 16的内存,但对于3GiB的分配则失败了。无论如何,显然这不是一个“错误”,因为gcc认为PTRDIFF_MAX是最大对象大小:https://developers.redhat.com/blog/2017/02/22/memory-error-detection-using-gcc/。`-Wall`启用了`-Walloc-size-larger-than=PTRDIFF_MAX`,在编译我的测试程序时我收到了该警告。 - Peter CordesPTRDIFF_MAX
)是指元素数量还是字节数量的限制?据我所见,这是一个元素数量的限制,但如果元素足够大,你很容易就会先达到字节的硬性限制(SIZE_MAX
)。然而,这是非常危险的,因为通过char *
访问该数组的函数可能会出现问题。 - alx - recommends codidactchar *
)。设置实现限制意味着GCC可以将更宽的类型的end-start
实现为指针的原始整数减法,并通过sizeof(T)
进行除法运算(当然要使用移位或乘法逆元)。如果即使在32位系统上,指针相隔2.1 GiB,也需要精确的结果,那么它将需要扩展精度或将进位标志旋转以除以2。 - Peter Cordes在考虑内存的情况下,数组的最大大小受到用于索引数组的整数类型的限制。
arr [idx]
与 *((arr)+(idx))
完全等效,这实际上是C标准定义 []
数组下标运算符的方式。)无论如何,这是所有C实现共享的限制,但在实践中,实现具有较小的限制,例如gcc的PTRDIFF_MAX。 - Peter Cordes我猜最大的理论数组应该是"unsigned long"的最大值(或者是最新标准/编译器支持的最大整数)。
long long
)。 - Peter Cordes我正在寻找一种确定数组最大大小的方法。这个问题似乎是在问同样的事情,所以我想分享我的发现。
起初,C语言没有提供任何函数来确定编译时可分配的数组元素的最大数量。这是因为它将取决于将要执行的机器中可用的内存。
另一方面,我发现内存分配函数(calloc()
和malloc()
)可以允许分配更大的数组。此外,这些函数允许您处理运行时内存分配错误。
希望这有所帮助。