C++ 从代码到可以执行的过程(预编译、编译、汇编、链接)(以Linux为参考)

参考阿秀的学习笔记

测试代码

#include<iostream>
using namespace std;
#define PI 3.14
int main(){
        //测试代码
        cout<<PI<<endl;
        cout<<"hello world"<<endl;
        return 0;
}

预处理

处理内容

  • 删除define,展开所有宏定义
  • 处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
  • 处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他 文件。
  • 删除所有的注释,“//”和“/**/”。
  • 保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重 复引用。
  • 添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是 能够显示行号。

示例

g++ main.cpp -E -o main.i

  • -E:表示预处理
  • -o表示目标文件
......
# 2 "main.cpp" 2

# 2 "main.cpp"
using namespace std;

int main(){

 cout<<3.14<<endl;
 cout<<"hello world"<<endl;
 return 0;
}

编译

编译

把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应 的汇编代码文件。

  • 词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分 割成一系列的记号。
  • 语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的 语法树是一种以表达式为节点的树。
  • 语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进 行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定 的语义。
  • 优化:源代码级别的一个优化过程。
  • 目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言 表示。
  • 目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移 来替代乘法运算、删除多余的指令等。

示例

g++ main.i -S -o main.s

  • -S 表示编译
        .file   "main.cpp"
        .text
        .section        .rodata
        .type   _ZStL19piecewise_construct, @object
        .size   _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:
        .zero   1
        .local  _ZStL8__ioinit
        .comm   _ZStL8__ioinit,1,1
.LC1:
        .string "hello world"
        .text
        .globl  main
        .type   main, @function
main:
.LFB1493:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movq    .LC0(%rip), %rax
        movq    %rax, -8(%rbp)
        movsd   -8(%rbp), %xmm0
        leaq    _ZSt4cout(%rip), %rdi
        call    _ZNSolsEd@PLT
        movq    %rax, %rdx
        movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rax
        movq    %rax, %rsi
        movq    %rdx, %rdi
        call    _ZNSolsEPFRSoS_E@PLT
        leaq    .LC1(%rip), %rsi
        leaq    _ZSt4cout(%rip), %rdi
        call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
        movq    %rax, %rdx
        movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rax
        movq    %rax, %rsi
        movq    %rdx, %rdi
        call    _ZNSolsEPFRSoS_E@PLT
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1493:
        .size   main, .-main
        .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB1983:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        cmpl    $1, -4(%rbp)
        jne     .L5
        cmpl    $65535, -8(%rbp)
        jne     .L5
        leaq    _ZStL8__ioinit(%rip), %rdi
        call    _ZNSt8ios_base4InitC1Ev@PLT
        leaq    __dso_handle(%rip), %rdx
        leaq    _ZStL8__ioinit(%rip), %rsi
        movq    _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
        movq    %rax, %rdi
        call    __cxa_atexit@PLT
.L5:
        nop
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1983:
        .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
        .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1984:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $65535, %esi
        movl    $1, %edi
        call    _Z41__static_initialization_and_destruction_0ii
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1984:
        .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
        .section        .init_array,"aw"
        .align 8
        .quad   _GLOBAL__sub_I_main
        .section        .rodata
        .align 8
.LC0:
        .long   1374389535
        .long   1074339512
        .hidden __dso_handle
        .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
        .section        .note.GNU-stack,"",@progbits

汇编

将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没 有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过 来,汇编过程有汇编器as完成。经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Linux 下)、xxx.obj(Window下)。

g++ main.s -s -o main.o

链接

将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

静态链接

编译链接可执行文件的时候,连接器从库中复制这些函数和数据并把它们和引用程序的其它模块组合起来创建最终的可执行文件。链接器主要完成以下两个任务:

  • 符号解析:每个符号对应于一个函数、一个全局变量或者一个静态变量
  • 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对于这些符号的引用,使得它们指向这个内存位置。
    在这里插入图片描述
缺点

浪费空间,每个可执行程序中对所有目标文件都要有一份副本,所以如果多个程序对同一个目标文件有依赖,会出现同一个目标文件

优点

运行速度快,因为可执行程序中已经具备了所有执行程序所需要的任何东西。

动态链接

把程序按照模块拆分成各个相对独立的部分,在运行的时候才把他们链接在一起。Linux上面是.so格式,Windows上面是dll文件。==在内存中,一个共享库的.text节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享
在这里插入图片描述

缺点
  • 性能损耗,每次执行程序都需要进行链接,所以性能会有一定的损失。
  • 如果有个计算机上面没有安装相应的运行库,动态编译的可执行文件就会不能运行。
优点
  • 共享库:多个程序在执行的时候共享同一份副本
  • 更新方便:更新的时候替换一下原来的目标文件就可以了,程序下一次运行的时候,新版本目标文件会被自动加载到内存并且链接起来。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>