我们在使用C语言或C++时,常用的变量定义范围有全局全局变量、全局静态变量、局部静态变量、函数内部局部变量等,我们也知道函数内部局部变量和全局变量重名、函数内部静态变量和全局静态变量重名时,都是使用的函数内部声明变量,那么编译器是如何做到的呢? 这就要从编译过程开始说起了.... 编译过程图片来自网络 图片来自网络 测试代码#include <iostream>using namespace std;int G_a = 0;int G_b;static int s_c=1;static int s_d;int fun(){ static int s_c=2; int G_b=2; static int s_e=2; int in_a = s_e++; return in_a;}int main() { int m_b = fun(); cout<<'m_b= '<<m_b<<endl; m_b = fun(); cout<<'m_b= '<<m_b<<endl; G_b=3; return 0;} 编译操作①Preprocessor(预处理): 处理一些#号定义的命令或语句(如#define、#include、#ifdef等),生成.i文件。 $ g++ -o test_variable.i -E test_variable.cpp ②Compiler(编译) 进行词法分析、语法分析和语义分析等,生成.s的汇编文件 $ g++ -o test_variable.s -S test_variable.i ③Assembler(汇编) 将对应的汇编指令翻译成机器指令,生成二进制.o目标文件 $ g++ -o test_variable.o -c test_variable.s ④Linker(链接)
在链接期,只在可执行程序中记录与动态链接库中共享对象的映射信息。在程序执行时,动态链接库的全部内容被映射到该进程的虚拟地址空间。其本质就是将链接的过程推迟到运行时处理。
在链接期,将静态链接库中的内容直接装填到可执行程序中。在程序执行时,这些代码都会被装入该进程的虚拟地址空间中。 $ g++ -o test_variable -c test_variable.o 编译后的中间文件解析*.i文件: 预编译时,把引用的头文件内容和当前对应cpp代码拷贝进来,并把名空间等进行展开,打开或关闭对应的编译宏。 *.s文件: 进行词法分析、语法分析和语义分析等,生成.s的汇编文件。此时会把变量和函数等根据是否全局、静态类型等命名成不冲突的其他名称。 从生成的文件可以看出来,初始化的全局变量、未初始化的全局变量、全局静态变量、局部静态变量、静态函数等,名称都是按照编译器的规则进行了原样保持或添加了其他字符做唯一标识。编译器这么做的原因,就是为了保证符号唯一性,从而能对应上编译出的静态地址。 备注:变量名在运行时不存在,它们在编译期间被丢弃。但是,编译器可能会发出存储在应用程序二进制文件中的调试信息,以允许开发人员调试应用程序.但是,这通常会在发布版(Release版本会去除-g信息或通过strip去除符号信息)中删除。 使用nm命令查看可执行文件内部的符号信息: *.o文件: 将对应的汇编指令翻译成机器指令,生成二进制.o目标文件。咱们用文本文件打开查看是乱码,但是可以通过https://gcc./工具在线查看汇编代码。 总结虽然我们在写代码时,编译器会自动帮我们做一些工作,平时多了解一些编译器内部实现原理,对我们的编码质量和编码效率提升还是很有帮助的。 |
|