【问题标题】:How is memory allocated in an array?数组中的内存是如何分配的?
【发布时间】:2020-11-03 04:46:01
【问题描述】:

假设我们有一个数组int arr[size] 和一个指向整数int *i 的指针。我们可以使用malloc(sizeof(int) * size) 将一组连续的内存块分配给*i。但是我的问题是int arr[size] 中的内存是如何分配的。在这种情况下,根据我的猜测分配内存块是由编译器完成的。编译器是否隐式进行malloc() 或类似的函数调用?当我们编写类似int arr[] = "You guys are a great help" 的东西时,编译器是如何完成动态内存分配的。

【问题讨论】:

  • int arr[size],如果 size 是编译时间常数,则内存分配在程序开始执行之前完成。否则,它在它出现的函数调用的堆栈中。
  • 对于大多数系统,包括数组在内的局部变量都存储在堆栈中。当函数被调用时,编译器会生成代码来为变量保留一块堆栈(通常通过操作一个或多个 CPU 寄存器)。
  • 编译器知道函数中变量的个数,以及每个变量的大小(包括数组,基本上和malloc一样计算大小)。
  • 堆栈溢出是指您的程序超出编译器创建的堆栈区域。例如,通过索引超出本地数组的范围。
  • 这里有很多误解。有关变量存储位置的说明,请参阅What gets allocated on the stack and the heap?

标签: c pointers


【解决方案1】:

有 4 种不同的情况。

a) 全局变量和静态局部变量。对于这些,数组通常内置在可执行程序的文件中(通常分为几个部分 - 一个用于代码,一个用于初始化的只读数据,一个用于初始化数据,一个用于“初始化为零”/未初始化的数据) .大多数情况下,如果数组被初始化(例如int foo[44] = { 1, 2, 3 };),那么它将被放入正常的读/写数据部分(.data),如果它没有被初始化(例如int foo[44];),它将被放入“初始化为零”/未初始化的数据部分 (.bss) 以减小文件的大小。在(例如).data 部分中分配空间与在可执行文件的.text 部分中为代码分配空间没有太大区别。在这种情况下内存不能被释放。

b) 局部变量(在函数内部,例如void foo(void) { int bar[44]; })。在这种情况下,编译器通常会在堆栈上为其创建空间(并且不会“初始化为零”)。如果它被显式初始化,那么编译器将生成代码来初始化它,或者使用指令(如果数组很小),或者通过从数据部分中的隐藏副本复制数据(其中隐藏副本的分配方式与它相同'一直是一个全局变量)。在这种情况下,内存不能被显式释放,但会被隐式释放(如果/当你从函数返回时)。

c) 线程本地存储(例如__thread int foo[])。在这种情况下,编译器通常会在线程本地存储中分配空间;如果数组被初始化,它还将在数据部分中创建一个隐藏副本(隐藏副本的分配方式与全局变量的分配方式相同)。在这种情况下内存不能被释放。

d) 动态分配的数组(例如int *myPointer = malloc(sizeof(int) * 44);)。对于这些,编译器不会在任何地方分配空间(运行时库会)。这是唯一可以显式释放内存的情况(例如free(myPointer);)。

注 1:上面的很多内容(我在“通常”使用的所有地方)都依赖于实现,编译器可能会做一些非常不同的事情;要么是因为“做不同”对编译器的输出更有意义,要么是因为编译器可以正确优化。

注 2:遗憾的是,大多数编译器无法正确优化。当数组没有被修改时,很难让他们使用.rodata 部分,即使你使用const。对于存在用于初始化的隐藏数组的情况,很难让他们分析内容并找到更复杂的模式(他们可以/将会对像int foo[] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; } 这样的简单模式做得很糟糕)。如果你有一个像int hugeArray[12345678] = {4, 5, 6} 这样的全局数组,他们不会将数组放在.bss 部分并初始化它以减小文件大小,即使这样做很简单,等等)。

【讨论】:

    猜你喜欢
    • 2013-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 2018-02-06
    相关资源
    最近更新 更多