C中的内存分配顺序 [英] Order of memory allocation in C

查看:52
本文介绍了C中的内存分配顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图了解计算机/操作系统/编译器(不确定谁拥有内存分配,因此是我的菜鸟问题)如何将内存地址分配给局部变量.

I am trying to understand how the computer/OS/compiler (not sure who owns memory allocation, hence my noob-ish questioon) assigns memory addresses to local variables.

我有这个简单的程序:

#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);
}

我不明白的主要有两件事.

There are two main things I dont understand.

  1. 为什么变量 i 获得更早的内存地址然后变量 arr 和变量 a/b 甚至更早?当您实际使用该变量或为其赋值时,它似乎有一些事情要做.

  1. Why does variable i get an earlier memory address then variable arr, and variable a/b even earlier than? It appears it has something to do when you actually use the variable or assign it a value.

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

How/Why does the OS (or whoever is responsible) use the same memory address for variable c, and variable i? Obviously i goes out of scope, but c was declared before.

这是程序的输出:

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 编译器上运行.

I am running on Ubuntu 18, gcc c99 7.4.0 compiler.

推荐答案

现代编译器通常不会使用任何简单的方法为对象分配内存.假设您收到了几种不同的物品,并被告知要有效地将它们存放在架子上.您可能不会只是按照您拿到物品的相同顺序将每个物品放在架子上.您可能会堆叠类似的对象(如果它们是可堆叠的),并以其他方式排列对象以有效利用空间.编译器做同样的事情.

Modern compilers typically do not assign memory to objects using any simple method. Suppose you were given several varied objects and told to store them on a shelf efficiently. You likely would not just put each object on a shelf in the same order you got them. You would probably stack similar objects (if they were stackable), and otherwise arrange objects to use space efficiently. Compilers do the same thing.

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

Suppose a compiler is going to assign memory to all the objects defined in a function. Rather than just read the function and assign memory as soon as it sees each definition, a compiler may read the entire function and remember information about all the definitions. Then it may organize all the objects of the same sizes together and then sort the objects by sizes.

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

One reason it does this is that computers often have alignment requirements or benefits. Objects that are four bytes wide often must be located at memory addresses that are multiples of four bytes. (One reason for this is that the connections between the processor and memory, and connections within the processor, are four bytes wide—they effectively use 32 wires to carry 32 bits. Moving 32 bits from place to place is easy, but shifting the bits in units of less than 32 bits requires additional devices inside the processor.) Since your question does not involve objects of different widths, I will not go into this aspect further.

由于编译器正在读取整个函数,它必须记住您定义的所有对象.在您的示例中,它包括 arrabc.为此,编译器使用一些数据结构来记住它们.您将学习的第一个数据结构是一个简单的列表.编译器可以保留已定义对象和名称的列表.它可以按照编译器看到的名称顺序保留列表——arrabc——或者它可以按字母顺序保留列表——aarrbc.或者它可能会按大小或其他特征将列表按顺序排列,例如 abcarr如果按大小排序.

Since the compiler is reading the entire function, it has to remember all the objects you define. In your example, it includes arr, a, b, and c. To do this, the compiler uses some data structure to remember them. One of the first data structures you will learn about is a simple list. The compiler could keep a list of defined objects and names. It could keep the list in the order the compiler sees the names—arr, a, b, c—or it could keep the list in alphabetical order—a, arr, b, c. Or it might keep the list in order by size or other features, perhaps a, b, c, arr if sorted by size.

然而,事实证明简单的列表是低效的.如果我们尝试按字母顺序保留一个列表,那么每次我们想在中间放置一个新名称时都必须移动元素.即使只是按照我们看到名称的顺序保存的列表,以便将新名称添加到末尾,不需要任何移动,当我们想要对数据做一些更有趣的事情时,比如按对齐方式对列表进行排序时,也会很麻烦要求或尺寸.

However, it turns out simple lists are inefficient. If we try keeping a list in alphabetical order, then elements have to be moved every time we want to put a new name in the middle. Even a list that is just kept in the order we see the names, so that new names are just added to the end, not requiring any movement, is troublesome when we want to do fancier things with the data, like sorting the list by alignment requirements or size.

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

So compilers use fancier data structures for managing this information. As the compiler sees definitions, it enters the names into its data structures, which may use a variety of methods for organizing the data. Later, when the compiler is allocating memory for all the objects, the order in which they are processed is a result of how the data structure organized them. It is not a clear or simple result of how the names appear in your source code.

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

So, in general, there is no reason to expect that a compiler will allocate memory in an order related to the order in which names appear in your source code.

除此之外,在大多数函数中,编译器根本不会为许多对象分配固定内存.编译器可能只将变量保存在处理器寄存器中,而不是内存中,或者它可能在函数执行期间的不同时间为变量使用不同的内存.在您的示例中,编译器必须为对象分配内存,因为您获取了它们的地址.在没有获取这些变量地址的代码中,编译器可能根本不会将它们存储在内存中——函数非常简单,处理器可以只使用处理器寄存器来完成工作,甚至在编译期间优化代码以删除其中一些.

More than this, in most functions, the compiler does not assign fixed memory to many objects at all. A compiler might hold a variable only in a processor register, not the memory, or it might use different memory for the variable at different times during the execution of the function. In your example, the compiler has to assign memory for the objects because you take their addresses. In code that did not take the addresses of these variables, the compiler likely would not store them in memory at all—the function is so simple, the processor could get the work done using just processor registers, or even optimizing the code during compilation to remove some of it.

这篇关于C中的内存分配顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆