gpt4 book ai didi

c - 初始化大型双数组时出现问题

转载 作者:行者123 更新时间:2023-12-02 07:13:24 26 4
gpt4 key购买 nike

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

#include <stdio.h>
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

如果将YRaw阵列的大小减小了10倍,一切都会正常进行。如果系统中有6GB RAM,那么为什么会出现错误?谢谢,Gkk

最佳答案

4000000 * sizeof(double)可能约为32MB。对于堆栈而言,这太大了,这就是为什么您要获得异常的原因。 (简而言之,堆栈溢出了。)

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

通常,自动分配仅应用于中小型对象。该阈值难以描述,但是在大多数情况下,其上限为32MB。

更新:

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

首先,有 text (sometimes called code ) segment。它包含执行的实际代码,通常包含任何常量数据。通常,保护文本段免受意外修改,在某些系统中,物理内存实际上可以在碰巧正在运行同一程序或使用同一共享库的进程之间共享。

接下来是 data and bss segments。这些段一起包含所有静态分配的变量。数据段包含初始化变量,而bss包含未初始化变量。它们之所以与众不同,是因为未初始化的变量仅在可执行文件中通过它们的单独地址和它们的总大小来知道。该信息用于从操作系统请求空白页,这是为什么未初始化的全局变量的值为0的一种解释。

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

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

所有全局变量仅根据是否已初始化而位于databss段中。如果它们在数据段中,那么它们将直接影响可执行文件的文件大小。无论哪种方式,如果链接器成功,那么在过程的整个生命周期中都将确保存在它们的存储。

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

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

以下是一些具体示例:

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

在这里, YRaw是一个局部变量,并具有自动存储类。当进入函数时,它在堆栈上分配,并在函数退出时自动释放。但是,这仅在堆栈有足够的空间时才起作用。如果不是这样,则堆栈会溢出,并且如果您很幸运,您会得到某种运行时错误来表明这一事实。 (如果您不那么幸运,则堆栈仍然会溢出,但是它会在分配给其他段(可能是文本段)的内存上写入数据,并且聪明的黑客可能能够执行任意代码。)
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一样,它对文件大小没有影响。

最后,它可以来自堆。如果它需要像在编译时分配一样在整个运行中都存在,则可以执行以下操作:
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()的调用一样,验证内存分配是否成功是明智的选择,但是对内存不足的响应可能比允许断言失败更好。

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

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

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

如果缓冲区需要不同的生存期,具有仅在运行时才知道的大小或在运行时响应外部事件而生存和死亡,则应将其与 malloc()和friends一起分配在堆上,并最终通过 free()或可能终止流程。

关于c - 初始化大型双数组时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3571340/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com