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

Tyl*_*ler 43 c memory variables symbol-table

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

我正在寻找关于措词" variable_name位于0xaaaaaaaa"的澄清.编译器如何识别字符串"variable_name"与该特定内存地址相关联?字符串"variable_name"是否存储在内存中?该编译器只是替代variable_name0xaaaaaaaa,只要自己认为呢,如果是这样,是不是必须使用的内存,以使该替换吗?

Car*_*rum 75

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

所以回答你的字面问题:

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

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

字符串是"variable_name"存储在内存中的某个位置吗?

仅在编译器运行时.

该编译器只是替代variable_name0xaaaaaaaa,只要自己认为呢,如果是这样,是不是必须使用的内存,以使该替换吗?

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

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

int x = 12;

int main(void)
{
    return x;
}
Run Code Online (Sandbox Code Playgroud)

很简单,对吧?好.我们来看看这个程序,并编译它并查看反汇编:

$ 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
Run Code Online (Sandbox Code Playgroud)

看到那条movl线?它抓取全局变量(在这种情况下,以指令指针的相对方式).再也不提了x.

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

int x = 12;

int main(void)
{  
    volatile int y = 4;
    return x + y;
}
Run Code Online (Sandbox Code Playgroud)

该程序的反汇编是:

(__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
Run Code Online (Sandbox Code Playgroud)

现在有两条movl指令和一条addl指令.你可以看到第一个movl是初始化y,它决定在堆栈上(基指针 - 4).然后下一个movl将全局x变为一个寄存器eax,并addl添加y到该值.但正如您所看到的,文字xy字符串不再存在.他们是为方便,程序员,但电脑肯定不能在执行时关心他们.

  • 关于C如何做的很好的解释.请注意,其他一些语言(特别是现代的"动态"或"脚本"语言)可能会维护数据的符号名称,并且它们确实在运行时使用内存来保留映射信息. (6认同)
  • 这是一个很好的答案.只有一个问题:编译器在真正运行之前如何知道地址?我认为内存是动态分配的(因此地址在不同的运行中可能会有所不同). (2认同)
  • @JacksonTale - 这在很大程度上取决于特定系统的配置方式,但在大多数常见系统中,虚拟内存意味着即使底层物理地址在不同运行之间发生变化,您的进程也始终具有相同的内存逻辑视图.在上面使用的示例中,变量是相对于指令指针而不是绝对地址. (2认同)

Mic*_*sen 10

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


jun*_*nix 6

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

换一种说法.一旦编译器运行,变量名就不再可用了


Jon*_*pan 6

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

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

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