变量名如何存储在C的内存中?

时间:2013-01-30 19:42:17

标签: c variables memory symbol-table

在C中,假设您有一个名为variable_name的变量。假设它位于0xaaaaaaaa,并且在该内存地址处,您有整数123.换句话说,variable_name包含123。

我正在寻找关于措辞的澄清“variable_name位于0xaaaaaaaa”。编译器如何识别字符串“variable_name”与该特定内存地址相关联?字符串“variable_name”是否存储在内存中?编译器是否只是在variable_name看到0xaaaaaaaa时将其替换为{{1}},如果是,那么它是否不必使用内存来进行替换?

5 个答案:

答案 0 :(得分:71)

变量名称在编译器运行后不再存在(除了共享库或调试符号中的导出全局变量等特殊情况)。整个编译行为旨在采用源代码所代表的符号名称和算法,并将其转换为本机机器指令。所以,是的,如果你有一个全局variable_name,并且编译器和链接器决定将它放在0xaaaaaaaa,那么无论在代码中使用它,它都只能通过该地址访问。

所以回答你的字面问题:

  

编译器如何识别字符串" variable_name"与该特定内存地址相关联?

工具链(编译器和链接器)协同工作以为变量分配内存位置。跟踪所有引用是编译器的工作,链接器稍后会放入正确的地址。

  

字符串"variable_name"是否存储在内存中?

仅在编译器正在运行时。

  

编译器是否只是在variable_name看到0xaaaaaaaa时将其替换为int x = 12; int main(void) { return x; } ,如果是,那么它是否必须使用内存才能进行替换?

是的,这几乎发生了什么,除了它是链接器的两阶段工作。是的,它使用内存,但它是编译器的内存,而不是程序运行时的任何内容。

一个例子可能会帮助您理解。让我们试试这个程序:

$ cc -Wall -Werror -Wextra -O3    example.c   -o example
$ otool -tV example
example:
(__TEXT,__text) section
_main:
0000000100000f60    pushq   %rbp
0000000100000f61    movq    %rsp,%rbp
0000000100000f64    movl    0x00000096(%rip),%eax
0000000100000f6a    popq    %rbp
0000000100000f6b    ret

非常简单,对吧?好。让我们来看看这个程序,并编译它并查看反汇编:

movl

看到x行?它抓取了全局变量(在这种情况下,以指令指针的相对方式)。不再提及int x = 12; int main(void) { volatile int y = 4; return x + y; }

现在让它变得更复杂并添加一个局部变量:

(__TEXT,__text) section
_main:
0000000100000f60    pushq   %rbp
0000000100000f61    movq    %rsp,%rbp
0000000100000f64    movl    $0x00000004,0xfc(%rbp)
0000000100000f6b    movl    0x0000008f(%rip),%eax
0000000100000f71    addl    0xfc(%rbp),%eax
0000000100000f74    popq    %rbp
0000000100000f75    ret

此程序的反汇编是:

movl

现在有两条addl条指令和一条movl指令。您可以看到第一个y正在初始化movl,它已决定将在堆栈上(基指针 - 4)。然后,下一个x将全局eax放入注册addlyx添加到该值。但正如您所看到的,文字y和{{1}}字符串不再存在。它们是程序员 you 的便利,但计算机在执行时肯定不关心它们。

答案 1 :(得分:9)

C编译器首先创建一个符号表,该表存储变量名与其在内存中的位置之间的关系。在编译时,它使用此表将变量的所有实例替换为特定的内存位置,正如其他人所说的那样。您可以在维基百科页面上找到更多相关信息。

答案 2 :(得分:6)

所有变量都由编译器代替。首先,它们被引用替换,然后链接器放置地址而不是引用。

换句话说。一旦编译器运行

,变量名就不再可用了

答案 3 :(得分:6)

这就是所谓的实施细节。虽然你所描述的是我曾经使用的所有编译器的情况,但并非必须如此。 AC编译器可以将每个变量放在一个哈希表中并在运行时查找它们(或类似的东西),事实上早期的JavaScript解释器就是这样做的(现在,它们进行Just-In-TIme编译,导致更原始的东西。)

特别适用于VC ++,GCC和LLVM等常见编译器:编译器通常会将变量分配给内存中的某个位置。全局或静态范围的变量获得一个在程序运行时不会改变的固定地址,而函数中的变量获得堆栈地址 - 即相对于当前堆栈指针的地址,每次调用函数时都会更改。 (这是过于简单化了。)一旦函数返回,堆栈地址就会变为无效,但具有有效零开销的好处。

一旦变量具有分配给它的地址,就不再需要变量的名称,因此将其丢弃。根据名称的类型,可以在预处理时(对于宏名称),编译时间(对于静态和局部变量/函数)和链接时间(对于全局变量/函数)丢弃名称。如果导出符号(对其他程序可见,以便他们可以访问它),名称通常会保留在“符号表”的某处, 占用了大量的内存和磁盘空间。

答案 4 :(得分:4)

  

编译器在看到它时只是将variable_name替换为0xaaaaaaaa

  

如果是这样,是否不得不使用内存来进行替换?

是。但它是编译器,在编译代码之后,你为什么关心内存?