首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C中内存分配的顺序

C中内存分配的顺序
EN

Stack Overflow用户
提问于 2019-09-28 16:05:21
回答 3查看 1.3K关注 0票数 2

我试图了解计算机/OS/编译器(不确定谁拥有内存分配,因此我的noob问题单)如何将内存地址分配给局部变量。

我有一个简单的程序:

代码语言:javascript
复制
#include <stdio.h>

int main(int argc, char** argv) {

    printf("hello, world\n");
    int arr[10];
    int a = 1;
    int b = 2;
    int c;
    for (int i = 0; i < 10; i++) {

        printf("Variable i: %p\n", &i);
        printf("Variable arr[i]: %p\n", &arr[i]);
    }
    printf("Variable a: %p\n", &a);
    printf("Variable b: %p\n", &b);
    printf("Variable c: %p\n", &c);
}

有两件事我不明白。

  1. 为什么变量I得到的内存地址早于变量arr,变量a/b甚至比变量早?当您实际使用变量或为其赋值时,它似乎有些事情要做。
  2. 如何/为什么操作系统(或负责人)对变量c和变量i使用相同的内存地址?显然,我超出了范围,但c是在之前声明的.

下面是程序的输出:

代码语言:javascript
复制
hello, world
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16970
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16974
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16978
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b1697c
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16980
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16984
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16988
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b1698c
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16990
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16994
Variable a: 0x7ffd60b16964
Variable b: 0x7ffd60b16968
Variable c: 0x7ffd60b1696c

我运行的Ubuntu 18,gcc c99 7.4.0编译器。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-09-29 11:38:54

现代编译器通常不会使用任何简单的方法为对象分配内存。假设有人给你几个不同的东西,让你把它们有效地存放在架子上。你不可能只是把每件物品放在一个架子上,按照你得到的顺序排列。您可能会堆叠类似的对象(如果它们是可堆叠的),并以其他方式安排对象以有效地使用空间。编译器也会做同样的事情。

假设编译器将为函数中定义的所有对象分配内存。编译器可以读取整个函数并记住有关所有定义的信息,而不是一看到每个定义就立即读取函数并分配内存。然后,它可以将所有大小相同的对象组织在一起,然后按大小对对象进行排序。

它这样做的一个原因是,计算机通常有对齐的要求或好处。宽度为四个字节的对象通常必须位于内存地址中,这些地址是四个字节的倍数。(原因之一是处理器和内存之间的连接,以及处理器内部的连接,都是四字节宽-它们有效地使用32条线来承载32位。将32位从一个地方移动到另一个地方是很容易的,但是以小于32位的单位来移动这些比特需要处理器内部的额外设备。)由于您的问题不涉及不同宽度的对象,我将不再进一步讨论这方面的问题。

因为编译器正在读取整个函数,所以它必须记住您定义的所有对象。在您的示例中,它包括arrabc。为此,编译器使用一些数据结构来记住它们。您将了解的第一个数据结构之一是一个简单的列表。编译器可以保存定义对象和名称的列表。它可以按照编译器看到的名称(arrabc-or)保持列表的顺序。它可以按字母顺序保持列表--aarrbc。或者,如果按大小排序,则可能会按大小或其他特性保持列表的顺序,比如abcarr

然而,事实证明,简单的列表效率很低。如果我们尝试按字母顺序保持一个列表,那么每次我们想要在中间放一个新名字时,元素都必须移动。即使是按照我们看到的名称的顺序保持的列表,所以新的名称只是添加到末尾,而不需要任何移动,当我们想要对数据做更好的事情时,比如按照对齐要求或大小排序列表,也是很麻烦的。

因此,编译器使用更高级的数据结构来管理这些信息。当编译器看到定义时,它会将名称输入到它的数据结构中,数据结构可以使用各种方法来组织数据。稍后,当编译器为所有对象分配内存时,处理它们的顺序取决于数据结构如何组织它们。这并不是一个明确或简单的结果,名称如何出现在您的源代码。

因此,一般来说,没有理由期望编译器按照与名称在源代码中出现的顺序相关的顺序分配内存。

更重要的是,在大多数函数中,编译器根本不为许多对象分配固定内存。编译器可能只在处理器寄存器中保存变量,而不是在内存中,或者在函数执行期间的不同时间对变量使用不同的内存。在您的示例中,编译器必须为对象分配内存,因为您接受对象的地址。在不使用这些变量地址的代码中,编译器可能根本不会将它们存储在内存中--函数非常简单,处理器可以使用处理器寄存器完成工作,甚至可以在编译过程中优化代码以删除其中的一些内容。

票数 3
EN

Stack Overflow用户

发布于 2019-09-28 16:24:00

C没有说明这些东西中的任何一个。整个问题涉及特定平台上某些特定编译器的内部细节。

标准所说的本质上就是不同的对象(变量等)。一定有不同的地址。这些地址是如何分配的,甚至是真正的地址:这些是实现细节。

为什么变量我得到一个较早的内存地址,然后是变量arr

因为这就是编译器决定要做的。它可以选择相反的顺序,或者把它们放在完全不同的存储区域,如果它愿意的话。如果需要的话,编译器可以选择在奇数天倒序。语言根本不指定任何内容,更不用说保证了。

--当您实际使用变量或给它赋值时,它似乎有一些事情要做。

一个好的优化者很可能选择这样做,因为它最小化了本地使用的存储量。但是(如果这听起来很熟悉的话就阻止我)这是一个实现细节。它可以用不同的编译器标志或者任何东西来改变。

票数 0
EN

Stack Overflow用户

发布于 2019-09-28 16:25:04

编译器确定可执行文件中变量的布局。实际地址由操作系统确定。

为什么变量I得到的内存地址早于变量arr,变量a/b甚至比变量早?当您实际使用变量或给它赋值时,它似乎有一些事情要做。

在默认情况下,可能是优化,也可能是在堆栈上分配数组的方式。更改变量的布局不会影响程序的执行。

如何/为什么操作系统(或负责人)对变量c和变量i使用相同的内存地址?显然,我超出了范围,但c是之前宣布的。

不使用变量c,因此程序的行为不依赖于ic的地址不同。如果为c分配一个值,则地址可能会更改。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58148075

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档