C++编译过程

How the C++ Compiler works?

文章目录

  • How the C++ Compiler works?
    • compiling
    • Examples
    • 总结
      • 欢迎关注公众号【三戒纪元】


通过编程,是的text程序编程可执行文件,基本上主要有2个操作发生:

  • compiling 编译
  • linking 链接

compiling

C++ 编辑器要做的就是把文本变成中间格式——obj,然后obj们会被传入到linking,linking会做所有linking的事。

编译期要做的几件事:

  • pre-process 预处理代码。所有预处理语句会在那时被评估。常见的预处理语句有:include, define, if和ifdef

    include 预处理语句很简单,预处理时会打开include的文件,读取所有内容,然后粘贴进我们引用的 #include的文档中。

    如果我们有2个文件:

    EndBrace.h

    }
    

    Math.cpp文件

    int Multipy(int a, int b) {int result = a * b;return result;
    #include "EndBrace.h"
    

    此时编译2个文件,会发现能够编译成功,就是因为预处理时将 }放到了Math.cpp文件中的#include "EndBrace.h"处。

    预编译一下看下结果:

    (base) randy@SanJieJiYuan:~/compiler$ gcc -E EndBrace.h Math.cpp
    # 1 "EndBrace.h"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 31 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 32 "<command-line>" 2
    # 1 "EndBrace.h"
    }
    # 1 "Math.cpp"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<command-line>" 2
    # 1 "Math.cpp"
    int Multipy(int a, int b) {int result = a * b;return result;
    # 1 "EndBrace.h" 1
    }
    # 4 "Math.cpp" 2
    
  • tokenizing 标记解释 和 parsing 解析阶段:将C++ 文本处理处理成编译器能懂和处理的语言。创建叫做 abstract syntax tree(抽象语法树),就是以抽象语法树的形式表达代码,这是CPU会处理的机器码。

    可以打开生成的 obj文件看看里面其实写的已经是二进制数据了,只不过通过16进制表示出来。

    编译出可以看出来的汇编指令,这是计算机处理的指令:

    (base) randy@SanJieJiYuan:~/compiler$ gcc -o randy.asm -S Math.cpp
    (base) randy@SanJieJiYuan:~/compiler$ cat randy.asm .file   "Math.cpp".text.globl  _Z7Multipyii.type   _Z7Multipyii, @function
    _Z7Multipyii:
    .LFB0:.cfi_startprocendbr64pushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6movl    %edi, -20(%rbp)movl    %esi, -24(%rbp)movl    -20(%rbp), %eaximull   -24(%rbp), %eaxmovl    %eax, -4(%rbp)movl    -4(%rbp), %eaxpopq    %rbp.cfi_def_cfa 7, 8ret.cfi_endproc
    .LFE0:.size   _Z7Multipyii, .-_Z7Multipyii.ident  "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0".section        .note.GNU-stack,"",@progbits.section        .note.gnu.property,"a".align 8.long    1f - 0f.long    4f - 1f.long    5
    0:.string  "GNU"
    1:.align 8.long    0xc0000002.long    3f - 2f
    2:.long    0x3
    3:.align 8
    4:
    

编译器的工作就是把代码转化为 constant data(常数资料),或者是instructions(指令)。同时会得到其他数据,比如某个地方存储着所有的 constant variables(常数变量)

编译器会为每个函数制作1个函数签名,由各种特殊字符和函数名组成,当程序编译时,编译器会通过查找函数签名将它们linking起来。当调用函数时,编译器就会生成1个call 指令。

Examples

建立1个Log.cpp 文件和Main.cpp文件

Log.cpp

#include<iostream>void Log(const char* message) {std::cout << message << std::endl;
}

Main.cpp

#include <iostream>void Log(const char* message);int main() {Log("Hello Randy!");std::cin.get();
}

编译不linking

g++ -c  Log.cpp Main.cpp
(base) randy@SanJieJiYuan:~/compiler$ ls
Log.cpp  Log.o  Main.cpp  Main.o

项目里的每个cpp,都会被编译器编译成一个obj,这些cpp文件也叫 translation unit(编译单元)

.
├── Log.cpp
├── Log.o
├── Main.cpp
└── Main.o-rw-rw-r-- 1 qiancj qiancj   92 613 23:10 Log.cpp
-rw-rw-r-- 1 qiancj qiancj 2904 613 23:11 Log.o
-rw-rw-r-- 1 qiancj qiancj  112 613 23:06 Main.cpp
-rw-rw-r-- 1 qiancj qiancj 2752 613 23:11 Main.o

这里我们看到cpp文件产生的obj文件远远大于原来的cpp文件大小,因为原始的cpp文件中均包含 #include<iostream>,包含的这个头文件很大,造成了最终编译出的obj文件也很大。

看看预处理后的文件,前面大部分都是 iostream 中的代码,因为 iostream 也会include 其他文件,因此文件会很大很大。

如果1个cpp文件包含其他cpp文件,则这些包含的文件整体就是1个编译单元,如果诸多cpp文件互不包含,则每个cpp文件就是1个编译单元。因此1个 cpp文件与1个编译单元不等同。

本质上你得意识到,C++根本不在乎文件,文件这种东西在c++里不存在,文件只是用来给编译器提供源码的某种方法

举个例子,java中 class(类)名必须跟文件夹相同,而文件结构也得跟package一样。之所以这样,因为java需要某些文件的存在,C++完全不是这回事。

C++中默认定义头文件以 .h 结尾,代码实现文件以 .cpp 结尾,就是告诉编译器以C++的方式编译。

总结

编译器拿到源文件,生成1个包含机器语言和其他我们定义的常数数据的obj文件,然后将它们链接成1个包含所有需要运行的机器代码的可执行文件。


欢迎关注公众号【三戒纪元】

本文链接:https://my.lmcjl.com/post/1733.html

展开阅读全文

4 评论

留下您的评论.