从编译流程看变量在内存中的存储位置

本文深入解析C++编译过程,从预编译、编译到汇编及链接阶段,详细介绍了每一步的具体操作及原理。通过实例,展示了如何将C++源代码转化为汇编语言,再进一步生成可重定位目标文件,最终链接成可执行文件的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.编译流程

https://blog.csdn.net/williamgavin/article/details/83867408     参考的详细过程

gcc -E main.c -o main.i                     预编译:将源代码main.c处理生成main.i。主要处理#注释或者宏定义等。

gcc -S main.i -o main.s                     编译成汇编语言:将main.i编译成汇编语言

gcc -c  main.s -o main.o                   汇编语言编译成可重定位目标文件

gcc main.o -o main.exe                    链接:将目标文件链接成可执行文件

2.预编译

主要是编译前处理。

3.编译成汇编语言

https://www.jianshu.com/p/e7a22923867f      函数栈帧的详细解释过程参考

下面是Math.cpp。

int t(int a, int b){
    return a + b;
}
int add(int a, int b){
    int c = 10;
    int d = 15;
    c = t(a, b);
    return c + d;
}

下面是它编译后的汇编语言

.file    "Math.cpp"
    .text
    .globl    _Z1tii
    .def    _Z1tii;    .scl    2;    .type    32;    .endef
    .seh_proc    _Z1tii
_Z1tii:
.LFB0:
    pushq    %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe    %rbp, 0
    .seh_endprologue
    movl    %ecx, 16(%rbp)
    movl    %edx, 24(%rbp)
    movl    16(%rbp), %edx
    movl    24(%rbp), %eax
    addl    %edx, %eax
    popq    %rbp
    ret
    .seh_endproc
    .globl    _Z3addii
    .def    _Z3addii;    .scl    2;    .type    32;    .endef
    .seh_proc    _Z3addii
_Z3addii:                                      //函数的名字是add 后面两个i表示两个参数,三个参数的函数会产生三个i
.LFB1:                                          
    pushq    %rbp                           //将调用这个函数的函数的栈帧的底部栈指针存进本函数栈帧。用于恢复调用本函数的函数。
    .seh_pushreg    %rbp
    movq    %rsp, %rbp                   //将栈顶指针数据赋值给栈底指针。这时进入到了新的函数栈帧。
    .seh_setframe    %rbp, 0
    subq    $48, %rsp
    .seh_stackalloc    48
    .seh_endprologue
    movl    %ecx, 16(%rbp)         //保存参数。本函数总共有2个参数。调用本函数的函数使用的参数数据在计数寄存器中。将数寄                                                   //存器中的数据存入上一个函数(也是就调用本函数的函数)的栈帧中。地址是栈底数据的上                                                        //16字节。也就是参数还是保存在调用函数的栈帧中的。
    movl    %edx, 24(%rbp)       //同理保存第二个参数。保存在地址是栈底数据的上24字节。
    movl    $10, -4(%rbp)         // 这里给int c 开辟栈空间  并赋值10
    movl    $15, -8(%rbp)          // 这里给int d 开辟栈空间  并赋值15
    movl    24(%rbp), %eax      //累加器放入参数b
    movl    %eax, %edx            //数据寄存器放入参数b
    movl    16(%rbp), %ecx      //计数寄存器放入参数a
    call    _Z1tii                         //调用t函数
    movl    %eax, -4(%rbp)      //将t的返回值给c。这里经过t函数,累加器里的值变为了返回值
    movl    -4(%rbp), %edx      //下面都是相加的过程了
    movl    -8(%rbp), %eax
    addl    %edx, %eax         //将两个寄存器中的数值相加,结果放入累加器寄存器中。返回值存放在累加寄存器中。
    addq    $48, %rsp
    popq    %rbp                //弹出数据,放入基址寄存器中。也就是本栈帧中存储的第一个数据:调用函数的栈底指针函数,用                                                //来返回,调用函数。
    ret
    .seh_endproc
    .ident    "GCC: (tdm64-1) 4.9.2"

每调用一次函数,就会增加一个函数栈帧,并移动到新的栈帧。给栈帧分配的空间由函数里使用的变量个数和大小决定。

int& a = add(5, 6);  //这里的使用方法是错误的。因为这时返回值在累加器寄存器中。没有地址所以不能用引用。

这时可以加上const   const int& a = add(5, 6);    这时候将引用变为静态变量,在栈中给其分配静态空间。这时a值不再能更改。这里相当于赋值,并不再是引用。相当于  const int a。

4.生成可重定位目标文件

可重定位目标文件格式详细解释参考:https://blog.csdn.net/Xindolia_Ring/article/details/80961363

编译器会将每个cpp文件编译成.o文件。在Windows中是.obj文件,但是编译生成的文件后缀还是.o.在Linux中是.o文件。

Windows可重定位目标文件格式是PE格式, Linux可重定位目标文件格式是ELF格式。两种格式都是从coff格式发展而来。

windows 利用objdump 可以查看PE格式文件内容

查看Math.o 可重定位目标文件 发现他的格式是   pe-x86-64

0.text 是代码段    这里存储二进制代码。已经编译成机器代码。

1.data 存放的是已经初始化的全局变量和已经初始化了的static变量

2.bss 存放的是未初始化的全局变量和未初始化的static变量

3.stack  heap  栈区和堆区  栈里面存放函数内部变量,前面汇编部分已经很清楚了。堆是动态存储区,前面几个都是静态存储区。动态意思是存储空间是不固定的随着程序的执行会增加和缩小。静态是从程序开始存储区就是固定了的。凡是 new 或者 malloc 开辟的空间都是在堆里的。

4.  xdata  pdata  data 都是存放已经初始化的全局变量和static变量的区域。   不同的是存储的物理空间的不同。data 是可寻址片内ram  xdata是可寻址片外ram  pdata 是分页寻址片外ram  idata是可寻址片内ram,允许访问全部内部ram。

5.链接

将可重定位目标文件链接为一个整体。

每个cpp生成一个.o文件。但是 .o文件之间可能互相调用。对于本文件里已经声明但是没有定义的函数,不确定外部文件有没有定义此函数。所以.o文件将函数地址空出。等链接的时候,将各个有调用关系的.o文件相互串联起来。也就是将汇编中的call 地址填上。

6.静态库文件,动态库文件,可执行文件

这三者都是和.o文件格式相同,PE格式或者ELF格式。

lib文件是.o文件的集合。实际上和exe可执行文件差不多,只不过没有main函数。他的生成也是由众多.o文件链接生成的。如果只有单个.o文件。那.o文件和生成的.lib文件是一样的。因为根本就不用链接。所以devc++ 添加外部静态链接的时候可以添加.a也可以添加.o。这里的.a就相当于.lib。

例如用objdump 查看exe文件:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值