• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

C/C++编译原理(1) | 概述

武飞扬头像
西瓜king
帮助1

前言

欢迎持续关注专栏:juejin.cn/column/7265…

在平时写代码时,我们大多数时间都是直接使用集成开发环境,比如Visual Stdio,这样的IDE一般都是将编译和链接过程一步完成,虽然很方便,但是却不利于我们理解代码的执行过程,以至于很多相关的bug无处下手。

这个系列就借阅读<<程序员的自我修养>>这本书的读书笔记,以及对其中的一些实战,尽量搞清楚这些原理,让写代码的时候能够做到"知其然,知其所以然"。

正文

我们就从最熟悉的Hello World说起,使用C语言在Linux下完成一个Hello World程序非常容易,首先是代码:

#include <stdio.h>
int main() {
        printf("Hello World\n");
        return 0;
}

假设这里的的文件名是hello.c,那么在命令行执行如下编译和运行指令即可:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ gcc hello.c
zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ ./a.out 
Hello World

就是gcc这个跟文件名这个简单的编译流程,其实可以分解为4个步骤,分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。

这个过程涉了一些文件以及处理器如下图所示:

学新通

而搞懂整个编译流程,也就是搞明白图中的所有过程。

预编译

第一个流程就是把源代码文件hello.c和一些头文件如stdio.h一起通过预处理器cpp编译成一个.i文件。这个过程在实际写代码的时候非常有用,它可以实现源码级别的抽象,这个在后面关于C 的学习中细说。

这一步的编译过程,相当于如下指令:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ gcc -E hello.c -o hello.i

或者

cpp hello.c > hello.i

关于gcc指令的用法,后面会不断地学习和熟悉,这里的-E就是表示进行预编译,而-o表示输出文件,而cpp命令的>符号是重定向符号,即把输出结果保存到hello.i中。

预编译的输入其实就是文件,而处理的代码就是文件中以#开始的预编译指令,主要的规则如下:

  • 将所有的#define删除,并且展开所有的宏定义。
  • 处理所有的条件编译指令,比如#if#ifdef#elif#else#endif等。
  • 处理#include预编译指令,也就是将被包含的文件插入到该预编译的位置,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件,这时就要避免头文件被多次包含。
  • 删除所有的注释。
  • 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或者警告可以显示行号。
  • 保留所有的#pragma编译器指令,因为编译器要用到。关于#pragma指令还是非常有用的,用于向编译器传递指示或者指令,用于控制编译过程、设置编译选项、调整编译器行为等。比如产生在编译时的提示信息。

编译

第二个流程就是编译,这个过程是将预处理产生的文件进行一系列的词法分析、语法分析、语义分析以及优化,最终产生汇编代码文件,这个部分是最核心且复杂的。

对于gcc编译套件来说,可以使用下面命令:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ gcc -S hello.i -o hello.s

同时在现代的GCC中会把预编译和编译这2个步骤给合并成一个,使用一个叫做cc1的程序可以直接完成,命令如下:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ /usr/lib/gcc/x86_64-linux-gnu/11/cc1 hello.c
 main
Analyzing compilation unit
Performing interprocedural optimizations
 <*free_lang_data> {heap 952k} <visibility> {heap 952k} <build_ssa_passes> {heap 952k} <opt_local_passes> {heap 952k} <remove_symbols> {heap 952k} <targetclone> {heap 952k} <free-fnsummary> {heap 952k}Streaming LTO
 <whole-program> {heap 952k} <fnsummary> {heap 952k} <inline> {heap 952k} <modref> {heap 952k} <free-fnsummary> {heap 952k} <single-use> {heap 952k} <comdats> {heap 952k}Assembling functions:
 <simdclone> {heap 952k} main
Time variable                                   usr           sys          wall           GGC
 phase setup                        :   0.00 (  0%)   0.00 (  0%)   0.00 (  0%)  1298k ( 68%)
 phase parsing                      :   0.01 (100%)   0.00 (  0%)   0.01 (100%)   531k ( 28%)
 lexical analysis                   :   0.01 (100%)   0.00 (  0%)   0.00 (  0%)     0  (  0%)
 parser (global)                    :   0.00 (  0%)   0.00 (  0%)   0.01 (100%)   329k ( 17%)
 TOTAL                              :   0.01          0.00          0.01         1896k

这2个方法都可以得到汇编输出文件hello.s,对于C语言来说,这个预编译和编译的程序是cc1,对于C 来说,对应的程序是cc1plus,所以实际上gcc这个命令只是对这些后台程序的包装,根据不同的参数要求去调用不同的程序。

打开hello.s如下所示:

        .file   "hello.c"
        .text
        .section        .rodata
.LC0:
        .string "hello world"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        leaq    .LC0(%rip), %rax
        movq    %rax, %rdi
        movl    $0, x
        call    printf@PLT
        movl    $0, x
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
        .section        .note.GNU-stack,"",@progbits

后面文章会一个一个地分析这些字段。

汇编

汇编器就是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编的过程相比于编译就简单多了,它没有复杂的语法和语义,只需要根据汇编指令和机器指令的对照表进行翻译即可。

同样可以使用汇编器as来进行汇编:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ as hello.s -o hello.o

当然也可以使用GCC的不同参数来完成:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ gcc -c hello.c -o hello.o

对于.o文件,它是标准的ELF文件,这里就不仔细说了,后面会详细分析这类文件。

链接

链接是最容易忽略和出错的环节,也是一个比较让人费解的过程,为何不直接输出可执行文件而是一个目标文件呢?

就比如这里简单的Hello World程序,想要使用链接器ld进行链接,需要执行下面指令:

zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ ld -static /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/11/crtbeginT.o -L/usr/lib/gcc/x86_64-linux-gnu/11 -L/lib hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/11/crtend.o /lib/x86_64-linux-gnu/crtn.o
zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ ls
a.out  hello.c  hello.o  hello.s
zhangyuanhao@zhangyuanhao-VirtualBox:~/Desktop/CPlusStudy/gcc$ ./a.out 
hello world

不得不说这是一个复杂的过程,其中的参赛非常多,看到最后的Hello World打印我相信又是另一种感受,会觉得整个编译过程非常不简单。

同样的,关于这个复杂的链接过程,也是放到后面文章仔细探讨。

总结

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbbfjk
系列文章
更多 icon
同类精品
更多 icon
继续加载