【问题标题】:How are memory addresses assigned in C?C中的内存地址是如何分配的?
【发布时间】:2021-01-25 17:24:44
【问题描述】:

我了解到数组的值是按照内存地址“并排”存储的,因此数组的名称是指向数组第一个值的指针:

#include <stdio.h>

int main() {
    int array[] = {1, 2, 3};
    printf("%d", *array);       // The first value of array: 1
    printf("%d", *(array + 1)); // The second value of array: 2

}

直觉上,我认为代码中一个又一个声明的变量只是简单地分配了相邻的内存地址。这个想法违背了数组在内存中的定义方式,因为我的代码中的所有变量都会组成一个大数组。

我的问题是,本质上,有没有办法知道一个变量的地址是什么,相对于我程序中定义的其他变量,而不是打印它的地址?

【问题讨论】:

  • 您使用的是数组,因此数组的所有元素在内存中都是连续的。但是,使用(例如):int main(void) { int a,b,c; },它们可以按任何顺序排列,甚至可以不连续。
  • 它与数组有关。在 C/C++ 中,可以保证数组中的元素在内存中是连续的。分配块的字节也将是连续的。没有其他保证。地址所在的位置还取决于类型或限定类型。具有自动存储持续时间的对象在程序堆栈上声明。 staticglobal 对象与具有已分配存储持续时间的对象一样存储在其他位置。
  • 这更多的是关于行为编译器和运行时,而不是语言,因为它们的行为在不断变化,所以没有明确的答案。该语言只是告诉您不应该关心(例如,不允许计算不属于同一数组或结构的变量的地址差异)。
  • 关于“这个想法违背了数组在内存中的定义方式,因为我的代码中的所有变量都会组成一个大数组”:这是一个不合逻辑的。数组元素在内存中相邻的事实并不意味着其他事物在内存中可能不再相邻,就像我把我的房子涂成白色的事实意味着你可能不会把你的房子涂成白色一样。

标签: c pointers memory-address


【解决方案1】:

直觉上,我认为代码中一个又一个声明的变量被简单地分配了相邻的内存地址。

C 编译器将您的代码(专为“C 抽象机器”设计)转换为任何发生的情况,以便以完全不同的语言创建相同的行为(例如目标 CPU 的机器代码)。

作为这种“转换为完全不同的东西”的一部分,局部变量通常不存在(由没有内存地址的寄存器代替,因为寄存器不是内存),即使它们确实存在,它们也可以在任何顺序或重叠(例如,相同的内存用于在不同时间使用的 2 个不同的局部变量)。

数组“更特别”,因为它们通常更大更难编译器优化(同时遵守定义抽象机器行为的语言规则);因此,数组的元素更有可能在内存中保持连续;但这不是任何形式的保证。

例如,考虑以下代码:

int foo(int bar) {
    int myArray[] = { 1, 2, 3, 4};

    if(bar < 0) return bar + myArray[0];
    if(bar > 0) return bar + myArray[2];
    return bar + myArray[1];
}

如果你编译这个(就像我在https://godbolt.org/ 使用godbolt 所做的那样)并检查输出,你可能会看到如下内容:

foo(int):
  test edi, edi
  js .L6
  lea eax, [rdi+3]
  mov edx, 2
  cmove eax, edx
  ret
.L6:
  lea eax, [rdi+1]
  ret

如您所见;数组根本不存在(并且所有数组元素都没有内存地址),因为在这种情况下,编译器足够聪明,可以优化。

同样的事情发生在您的代码中(数组不再存在并且根本没有地址)。变成了这样:

.LC0:
  .string "%d"
main:
  sub rsp, 8
  mov esi, 1                  // The value "1" originally came from the array
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax
  call printf
  mov esi, 2                  // The value "2" originally came from the array
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax
  call printf
  xor eax, eax
  add rsp, 8
  ret

我的问题是,本质上,有没有办法知道变量的地址是什么。

基本上;不。这就像喂一匹马胡萝卜,然后试图确定在马大便后原始胡萝卜的分子将在哪里结束。

您唯一能做的就是在运行时获取地址(例如使用&amp;variable),其中(当且仅当编译器无法证明获​​取地址的代码可以被丢弃/忽略时)具有强制编译器确保变量确实有地址的副作用。

【讨论】:

  • 这不是一个真正正确的答案,因为它使用了“如何实际实现 C 程序”的模型,但是在“C 程序语义如何在 C 中描述”的模型中提出了问题标准。”值得注意的是,C 标准将数组定义为“连续分配的非空对象集”(C 2018 6.2.5 20),并以此为基础提出了问题。回答数组不一定实现为内存中的连续对象并不能回答关于 C 语义是什么的问题。
  • @EricPostpischil:不。问题显然是在讨论 C 转换后会发生什么(例如编译后的内存地址和堆栈布局);标准的“好像规则”影响标准的所有其他部分;这样“数组被定义为连续对象”实际上意味着“数组被定义为好像它们是 C 抽象机器中的连续对象,但可能不是在编译之后”。
  • 问题中没有任何内容表明它在询问 C 模型之外的任何内容。其中的每个单词都与 C 模型中的语义一致(在 OP 声明的信念范围内)。
  • @EricPostpischil:局部变量的内存地址(不是指针)(在不存在的堆栈上)?
  • 是的。 C 标准在讨论其模型中的存储位置时多次使用“内存”和“地址”,问题中不包含“本地”一词。
猜你喜欢
  • 1970-01-01
  • 2020-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多