问题描述
我了解到,数组的值是按照内存地址“并排”存储的,因此数组的名称是指向数组第一个值的指针:
#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
}
直觉上,我认为在代码中一个接一个声明的变量只是简单地分配了相邻的内存地址。这个想法与在内存中定义数组的方式背道而驰,因为我代码中的所有变量都将组成一个大数组。
从本质上讲,我的问题是,有没有一种方法可以知道变量相对于程序中定义的其他变量的地址,而不用打印其地址?
解决方法
直觉上,我认为在代码中一个接一个声明的变量只是简单地分配了相邻的内存地址。
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
如您所见;数组根本不再存在(并且所有array元素都没有内存地址),因为在这种情况下,编译器足够聪明以进行优化。
您的代码也发生了同样的事情(数组不再存在并且根本没有地址)。变成这样:
.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,eax
call printf
xor eax,eax
add rsp,8
ret
本质上,我的问题是,有没有一种方法可以知道变量的地址是什么。
本质上;没有。这就像喂胡萝卜,然后尝试确定大便后原始胡萝卜的分子将在哪里结束。
您唯一可以做的就是在运行时获取地址(例如,使用&variable
),该地址(当且仅当编译器无法证明获取地址的代码可以被丢弃/忽略)具有强制编译器确保变量确实具有地址的副作用。