C语言中的数组索引限制

3
在Linux系统上,拥有16GB的内存,为什么会出现以下段错误(segfault):
#include <stdlib.h>

#define N 44000

int main(void) {
    long width = N*2 - 1;
    int * c = (int *) calloc(width*N, sizeof(int));
    c[N/2] = 1;
    return 0;
}

根据GDB,问题出在c[N/2] = 1这里,但是原因是什么呢?

你在64位架构上编译/运行吗? - nos
2
即使在Linux 64位系统上,用户限制设置等问题也可能会妨碍工作。我记得我曾在一家公司工作,那里有32GB内存的计算机,但我只被允许使用4GB内存来处理一个用户进程。 - Johannes Schaub - litb
但是你确定你正在使用64位编译器吗?如果打印出 sizeof(long),它是多少? - Pavel Minaev
@pavel: printf("%ld\n", sizeof(long)); = 8 - adk
你可能也同时在后台运行了Firefox、电影播放器和OpenOffice吗? - Johannes Schaub - litb
7个回答

6

很可能是因为calloc的返回值是NULL。

你的计算机的物理内存大小并不直接关联到你可以使用calloc/malloc/realloc分配的内存大小。这个大小更多地取决于进程可用的虚拟内存大小。


2
根据我的计算(假设int为4个字节),您正在向操作系统请求约14GB的连续空间,我可以看到在这种情况下可能会失败。 - fbrereto
我没有计算,我直接写代码了。:) printf("%lu\n", (width*N) * sizeof(int)); 输出的结果是2602922112。 - bbum
但是,编译为64位后,它的大小为15487824000 -- 14.4GB,这就引出了一个问题,为什么Linux的calloc()不能返回一个14GB的内存块。 - bbum
1
是的 - 我搞砸了溢出。糟糕的程序员,没有咖啡。等等。再来杯咖啡。 - bbum
1
哇,我真的看不懂 - 我把width*N读成了width。/自己脑袋敲一下 - Will Bickford
显示剩余5条评论

6
你的计算超出了32位有符号整数的范围,这是“long”可能具有的范围。你应该使用size_t代替long。这可以保证能够容纳系统可以分配的最大内存块的大小。

系统可以分配的最大内存块永远不是2的幂次方数。共享内存、库映射、文件映射、运行时元数据等都将占用一些分配的子集。 - bbum
我特别建议他的程序崩溃不是因为他要求太多的RAM,而是因为他无意中将负数作为calloc的参数传递。 - Tyler McHenry
无论是否有共享库占用空间,大小最终都将是2的幂次方。如果他在32位Linux上,他将会1. 溢出calloc的参数 2. 耗尽地址空间 - 即使没有溢出,calloc也会失败。 - nos
在64位Linux(以及所有其他Unix系统)上,GCC遵循LP64模型 - 也就是说,指针和“long”都是64位。请参见http://gcc.fyxm.net/summit/2003/Porting%20to%2064%20bit.pdf。 - Pavel Minaev
修改了问题,以便取消我那个抽搐般的猴子踩踏。很抱歉。 - bbum
显示剩余2条评论

4
您正在分配约14-15 GB的内存,但由于某种原因,分配器此时无法提供您所需的内存- 因此calloc返回NULL并导致您解除引用空指针而发生段错误。

请检查calloc是否返回NULL。

这是假设您在64位Linux下编译64位程序。如果您在进行其他操作- 如果长整型在您的系统上不是64位,则可能会将计算溢出到calloc的第一个参数。

例如,请尝试

#include    <stdlib.h>
#include    <stdio.h>

#define N    44000L

int main(void)
{
    size_t width = N * 2 - 1;
    printf("Longs are %lu bytes. About to allocate %lu bytes\n",
           sizeof(long), width * N * sizeof(int));
    int *c = calloc(width * N, sizeof(int));
    if (c == NULL) {
        perror("calloc");
        return 1;
    }
    c[N / 2] = 1;
    return 0;
}

它确实返回了 null,有没有办法解决这个问题? - adk

2
您可能需要将 #define 更改为:
#define N    44000L

请确保在长分辨率下进行数学计算。您可能会为 calloc 生成一个负数。

calloc 可能会失败并返回 null,这会导致问题。


除非int是16位的,否则这不会有任何影响,这似乎不太可能。 44000 * 2-1适合32位int,然后被分配给long“width”,然后在long中计算width * N,因为那是较大的类型,然后calloc将在内部使用size_t。 - Steve Jessop

2
您正在请求2.6GB的RAM(不,您不是——在64位系统上您请求了14GB……在32位系统上,2.6GB超出了截断计算)。显然,Linux的堆被充分利用,以至于无法一次性分配这么多内存。

在Mac OS X上(包括32位和64位),这个问题可以解决,但只是勉强解决(并且在具有不同dyld共享缓存和框架的不同系统上可能会失败)。

当然,在任何系统上都应该可以很好地工作,特别是在64位系统上(即使32位版本的错误计算也可以工作,但仅仅是巧合)。

还有一个细节:在“真实世界的应用程序”中,随着应用程序的复杂性和/或运行时间的增加,最大连续分配将大大减少。使用堆越多,可用于分配的连续空间就越少。


1
创建一个14GB的文件,并将其内存映射。

1

很可能是因为 calloc() 无法满足请求而返回了NULL,所以试图引用c导致了段错误。您应该始终检查*alloc()的结果,以确保它不是NULL。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接