02--编译过程

2020-09-28  本文已影响0人  修_远

[TOC]

通过本章的学习,要了解到以下几个问题

  1. 被隐藏了的过程
  2. 编译器做了什么
  3. 链接器和编译器
  4. 模块拼装——静态链接

被隐藏的过程

对于平常的应用程序开发,我们很少选用关注编译和链接过程,因为通常的开发环境都是流行的集成开发环境(IDE),比如xcode。IDE一般都将编译和链接的过程一步完成,这个过程被成为构建(Build)。
为何要深入了解这些被隐藏的过程?
IDE和编译器提供的默认配置、编译和链接参数对于大部分的应用程序开发而言已经足够使用了。但是在这样的开发过程中,我们往往被这些复杂的集成工具所提供的强大的功能所迷惑,很多系统软件的运行机制与机理被掩盖,

#include <stdio.h>

int main() 
{
    printf("Hello World\n");
    return 0;
}

譬如说,下面的一个简单的编译命令:

gcc hello.c

编译结果会生成一个a.out文件,虽然只有简单的命令,但是它的过程并不简单。这个过程可以分解为四个步骤:预处理(Processing)、编译(Compilation)、汇编(Assembly)、链接(Linking)

02-snap001.png

预编译

预编译命令:( -E 表示只进行预编译)

gcc -E hello.c -o hello.i
  1. 处理源代码文件hello.c相关的头文件。例如:stdio.h 等被预编译器cpp预编译成一个 .i文件。(在c++中,源文件扩展名为 .cpp/.cxx,头文件扩展为 .hpp,预编译后的文件扩展名为 .ii

  2. 处理源代码文件中,以”#“开头的预编译指令。例如 #include、#define 等。处理规则有以下几点:

    • 将所有的 #define 删除,并且扩展所有的宏定义。
    • 处理所有条件预编译指令,例如 #if、#ifdef、#elif、#else、#endif
    • 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。(这个过程是递归进行的,也就是被包含的文件可能还包含其他文件)
    • 删除所有的注释 ///**/
    • 添加行号和文件标识,比如:# 2 "hello.c" 2,以便编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
    • 保留所有的 #pragma 编译器指令,因为编译器需要使用他们。

查看预编译后的文件,可以确定:

编译

编译过程就是把所有预处理完的文件进行一些列词法分析、语法分析、语义分析及优化后产生相应的汇编代码文件。这个过程是整个程序构建的核心部分,也是最复杂的部分之一。

  1. 编译上面预编译之后的文件 hello.i
gcc -S hello.i -o hello.s

来看看结果,感受一下汇编的魅力😏

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 15    sdk_version 10, 15, 6
    .globl    _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    $0, -4(%rbp)
    leaq    L_.str(%rip), %rdi
    movb    $0, %al
    callq    _printf
    xorl    %ecx, %ecx
    movl    %eax, -8(%rbp)          ## 4-byte Spill
    movl    %ecx, %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz    "Hello World\n"


.subsections_via_symbols
  1. 也可以使用 gcc 直接编译 hello.c 文件
gcc -S hello.c -o hello2.s

hello2.s 文件内容与上面贴出来的内容一模一样,我们要知道这个过程经历了 预编译+编译 两个过程即可

gcc是什么?

这个命令其实是包装过的命令,会根据不同的参数去调用预编译程序cc1、汇编器as、链接器ld

汇编

汇编器的定义

汇编器是将汇编代码转变成可以执行的指令,每一个汇编语句几乎都对应一条机器指令。

汇编器的特点

链接

需要搞懂的几个问题

  1. 为什么汇编器不直接输出可执行文件而是输出一个目标文件?
  2. 链接过程到底包含什么内容?
  3. 为什么要链接?

ld 链接示例

ls -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-linux-gnu/4.1.3 -L/usr/lib -L/lib hello.o --start-group -lgcc -lgcc_he -lc --end-group /usr/lib/gcc/i486-linux-gnu/4.1.3/crtend.o /user/lib/crtn.o

这是从书中手敲下来的一段示例,书中介绍的是Linux环境下的链接器,而我的电脑是Macos,示例中的一些目标文件无法获取,所以这行代码是没有运行的,但这并不影响我们来理解 链接器ld。
在终端中执行:man ld,可以查看 链接器ld 的用法,截取前面一段内容如下所示

NAME
     ld -- linker

SYNOPSIS
     ld files...  [options] [-o outputfile]

DESCRIPTION
     The ld command combines several object files and libraries, resolves references, and pro-
     duces an ouput file.  ld can produce a final linked image (executable, dylib, or bundle), or
     with the -r option, produce another object file.  If the -o option is not used, the output
     file produced is named "a.out".

其他的命令后面再具体分析,这里就不做过多的解释了。

上一篇下一篇

猜你喜欢

热点阅读