初始化大型双数组的问题

来自新C程序员的愚蠢问题……我在以下代码中遇到了分段错误:

#include  int main(void) { double YRaw[4000000]={0}; return 0; } 

使用GDB,我收到以下评论:

 Program received signal EXC_BAD_ACCESS, Could not access memory. Reason: KERN_PROTECTION_FAILURE at address: 0x00007fff5dd7b148 0x0000000100000f24 in main () at talk2me.c:18 18 double YRaw[4000000]={0}; // set YRaw[memdepth] so index is 0 to memdepth-1 

如果我将YRawarrays的大小减少10倍,那么一切都有效。我在系统中有6GB的RAM,那么为什么我会收到错误? 谢谢,Gkk

4000000 * sizeof(double)可能大约为32MB。 这对于堆栈来说太大了,这就是为什么你得到一个例外。 (简而言之,堆栈溢出。)

使用malloc()从堆中分配它,使其成为static ,或使其成为全局。

通常,自动分配仅应用于小到中等大小的对象。 阈值难以表征,但在大多数情况下32MB远远高于它。

更新:

进程中有几个内存区域。 (请注意,为了清楚起见,我将简化此操作,如果您需要详细信息,请阅读ld的文档,并找到实际用于为您的平台布置可执行文件内存的链接器控制文件。)

首先,有text (有时称为code )段 。 它包含执行的实际代码,通常包含任何常量数据。 通常,文本段受到保护以免意外修改,并且在一些系统中,物理内存实际上可以在恰好运行相同程序或使用相同共享库的进程之间共享。

接下来,有databss段 。 这些段一起保存所有静态分配的变量。 数据段包含初始化变量,bss包含未初始化的变量。 它们是不同的,因为未初始化的变量仅在可执行文件中通过其各自的地址及其总大小来识别。 该信息用于从操作系统请求空白页面,这是为什么未初始化的全局变量已知值为0的一种解释。

然后是stackheap 。 这两个段都是在运行时创建的,从加载时分配给进程的内存创建,并且通常在执行期间扩展。 堆栈逻辑上保存了调用嵌套,参数和局部变量的记录,尽管平台之间的细节可能会出乎意料地不同。 堆是由malloc()及其朋友(通常是C ++中的operator new()管理的malloc()池。 在许多平台中,进程内存映射的排列使得堆栈和堆不会交互,但这可能意味着堆栈的总大小有上限,而堆通常由虚拟内存系统限制。

以此为背景,让我们澄清每个声明的存储位置。

所有全局变量都基于它们是否被初始化而落在databss段中。 如果它们在数据段中,那么它们将直接贡献可执行文件的文件大小。 无论哪种方式,如果链接器成功,那么它们的存储将保证在进程的生命周期内存在。

声明为static变量与全局变量的分配相同,但没有公共符号表条目。 这意味着未初始化的static缓冲区位于bss段中,而初始化的static数据段位于数据段中,但其名称仅为可以查看其定义的范围所知。

堆中的分配不保证在运行时成功。 由系统策略和硬件体系结构强制执行的进程总大小的上限。 例如,在典型的32位系统上,该架构不允许任何单个进程使用超过4GB的地址。

以下是一些具体的例子:

 int foo(void) { double YRaw[4000000]={0}; // ... do something with huge buffer } 

这里, YRaw是一个局部变量,具有自动存储类。 输入function时,它在堆栈中分配,并在function退出时自动释放。 但是,只有当堆栈有足够的空间时,这才有效。 如果没有,那么堆栈溢出,如果你很幸运,你会得到某种运行时错误来表明这一事实。 (如果你不是那么幸运,堆栈仍然溢出,但它写在分配给其他段的内存之上,可能是文本段,而聪明的黑客可能能够执行任意代码。)

 static double YRaw2[4000000]={0}; int foo(void) { static double YRaw3[4000000]={0}; // ... do something with huge buffer } 

这里, YRaw2YRaw3被初始化,最终都在数据段中,并且在许多平台上将使实际可执行文件包含您指定为其初始值的400万0.0值。 两个缓冲区之间的唯一区别在于范围问题。 YRaw2可以由同一模块中的任何函数使用,而YRaw3仅在函数内可见。

 static double YRaw4[4000000]; int foo(void) { static double YRaw5[4000000]; // ... do something with huge buffer } 

在这里, YRaw4YRaw5都在bss段中结束​​,并且通常不会放大可执行文件本身。 同样,缓冲区的名称范围不同。 它们将被隐式初始化为与程序启动时为YRaw2YRaw3指定的值相同的0值。

 double YRaw6[4000000]; int foo(void) { // ... do something with huge buffer } 

这里, YRaw6类似于上面的YRaw4 ,除了名称具有全局范围,缓冲区可以与其他模块共享,也可以与该模块中的每个函数共享。 它存储在bss段中,因此与YRaw4一样, YRaw4文件大小没有影响。

最后,它可以来自堆。 如果它需要在整个运行中存在,就好像它是在编译时分配的那样,你可以这样做:

 int foo(void) { static double *YRaw7 = NULL; if (!YRaw7) { // allocate the buffer on the first use YRaw7 = calloc(4000000, sizeof(double)); } // ... do something with huge buffer } 

在这里, YRaw7存储在堆中,在第一次使用时分配,并且在进程退出之前永远不会释放。 在大多数“合理”平台上,这种使用模式既合理又允许。

 int foo(void) { double *YRaw8 = calloc(4000000, sizeof(double)); assert(YRaw8 != NULL); // do something with huge buffer // ... // but be careful that all code paths that return also // free the buffer if it was allocated. free(YRaw8); } 

在这里, YRaw8具有与原始示例所预期的自动变量相同的生命周期,但实际存储在堆中。 在调用assert() ,validation内存分配是否成功可能是明智的,但是对于缺少内存可能没有比允许断言失败更好的响应。

另一个微妙之处:我使用calloc()来分配缓冲区。 这具有很好的特性,即如果分配成功,则保证存储器被初始化为全零位。 但是,这有副作用,它(通常)必须写入分配的每个字节才能产生这种效果。 这意味着虚拟内存的所有页面不仅被分配给进程,而且每个页面都必须被分页并写入每个字节。 使用malloc()通常会表现得更好,但代价是内存不能保证清晰。

最后,显而易见的问题是“无论如何我应该在哪里分配缓冲区?”

我不能给你一个强硬的规则,除了大的分配永远不属于堆栈。 如果缓冲区需要在进程的生命周期中存在,那么未初始化的static (无论是在模块还是函数范围)通常是正确的答案。

如果缓冲区需要不同的生命周期,只有在运行时才知道一个大小,或者为了响应运行时的外部事件而生存和死亡,那么它应该在堆上用malloc()和朋友分配,并最终发布free()或可能终止进程。

您试图在堆栈上声明整个数组。 即使你有一个太字节的RAM,也只有一个小的,固定的部分专用于堆栈空间。 需要使用malloc在堆上分配大量数据:

 #include  int main(void) { double* YRaw = malloc(4000000 * sizeof(double)); memset(YRaw, 0, 4000000 * sizeof(double)); /* ... use it ... */ free(YRaw); /* Give the memory back to the system when you're done */ return 0; } 

另请参阅: “堆栈和堆的内容和位置是什么?”