文章摘要
汇编代码中的地址并非在编译阶段就固定,而是经过多阶段处理才最终确定。在编译阶段,变量和函数仅以符号名或相对偏移形式存在;汇编阶段将符号转换为目标文件的段内相对地址;链接阶段分配虚拟地址;最终在程序加载时,操作系统将虚拟地址映射到物理内存。整个过程表明,具体地址是在链接和加载阶段才确定的,而非编译期间。局部变量则始终通过寄存器偏移访问,没有绝对地址。
1. 汇编代码中的“地址”是什么?
- 在汇编代码(.s文件)中,变量、函数等通常以符号名(如
main
、foo
、var
)出现,或者以相对偏移(如4(%rbp)
)出现。 - 只有极少数情况下(如绝对地址访问),才会出现具体的数值地址,但这通常是特殊用途。
2. 编译期间发生了什么?
- 编译器(如gcc)将C代码翻译成汇编代码(.s文件),此时还没有具体的内存地址,只有符号和相对偏移。
- 汇编器(如as)将汇编代码翻译成目标文件(.o),此时每个符号有一个相对地址(段内偏移),但还不是最终的内存地址。
3. 链接期间发生了什么?
- 链接器(如ld)将多个.o文件和库文件合并成一个可执行文件(如ELF、PE等)。
- 链接器会为每个段(.text、.data、.bss等)分配虚拟地址,并把符号解析为具体的段内偏移或虚拟地址。
- 这时,可执行文件中的地址是虚拟地址,但还不是进程运行时的物理地址。
4. 加载期间发生了什么?
- 当操作系统加载可执行文件时,会把各个段映射到进程的虚拟地址空间。
- 这时,代码和数据的虚拟地址才最终确定,物理地址由操作系统的内存管理单元(MMU)动态分配。
5. 例子说明
5.1 源代码
int global_var = 42;
int main() {
return global_var;
}
5.2 汇编代码(gcc -S)
.globl global_var
.data
global_var:
.long 42
.globl main
.text
main:
...
movl global_var(%rip), %eax
...
- 这里
global_var
是一个符号,没有具体地址。
5.3 目标文件(.o)
global_var
有一个段内偏移,比如.data段的第0个字节。
5.4 可执行文件
- 链接器把.data段安排到虚拟地址0x601000(举例),那么
global_var
的虚拟地址就是0x601000。
5.5 运行时
- 操作系统把0x601000映射到物理内存的某个位置,进程通过虚拟地址访问。
6. 局部变量的情况
- 局部变量在栈上,汇编代码用
%rbp
、%rsp
等寄存器加偏移访问(如-4(%rbp)
),没有绝对地址,只有相对偏移。
7. 总结
- 编译期间,只有符号和相对偏移,没有最终的具体地址。
- 链接期间,符号被分配到虚拟地址,但这只是可执行文件中的地址。
- 加载期间,操作系统把虚拟地址映射到物理内存,进程通过虚拟地址访问。
- 只有在程序运行时,地址才最终确定。
结论
汇编代码中的地址安排不是在编译期间就确定好具体地址的,而是在链接和加载阶段才最终确定。编译期间只有符号和相对偏移。