zoukankan      html  css  js  c++  java
  • gcc 编译过程详解-(转自CarpenterLee)

    前言

    C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程。

    编写hello world C程序:

    // hello.c
    #include <stdio.h>
    int main(){
        printf("hello world!
    ");
    }

    编译过程只需:

    $ gcc hello.c # 编译
    $ ./a.out # 执行
    hello world!

    这个过程如此熟悉,以至于大家觉得编译事件很简单的事。事实真的如此吗?我们来细看一下C语言的编译过程到底是怎样的。

    上述gcc命令其实依次执行了四步操作:1.预处理(Preprocessing), 2.编译(Compilation), 3.汇编(Assemble), 4.链接(Linking)。

    示例

    为了下面步骤讲解的方便,我们需要一个稍微复杂一点的例子。假设我们自己定义了一个头文件mymath.h,实现一些自己的数学函数,并把具体实现放在mymath.c当中。然后写一个test.c程序使用这些函数。程序目录结构如下:

    ├── test.c
    └── inc
        ├── mymath.h
        └── mymath.c
    

    程序代码如下:

    // test.c
    #include <stdio.h>
    #include "mymath.h"// 自定义头文件
    int main(){
        int a = 2;
        int b = 3;
        int sum = add(a, b); 
        printf("a=%d, b=%d, a+b=%d
    ", a, b, sum);
    }

    头文件定义:

    // mymath.h
    #ifndef MYMATH_H
    #define MYMATH_H
    int add(int a, int b);
    int sum(int a, int b);
    #endif

    头文件实现:

    // mymath.c
    int add(int a, int b){
        return a+b;
    }
    int sub(int a, int b){
        return a-b;
    }

    1.预处理(Preprocessing)

    预处理用于将所有的#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。gcc的预处理是预处理器cpp来完成的,你可以通过如下命令对test.c进行预处理:

    gcc -E -I./inc test.c -o test.i

    或者直接调用cpp命令

    $ cpp test.c -I./inc -o test.i

    上述命令中-E是让编译器在预处理之后就退出,不进行后续编译过程;-I指定头文件目录,这里指定的是我们自定义的头文件目录;-o指定输出文件名。

    经过预处理之后代码体积会大很多:

    X文件名文件大小代码行数
    预处理前 test.c 146B 9
    预处理后 test.i 17691B 857

    预处理之后的程序还是文本,可以用文本编辑器打开。

    2.编译(Compilation)

    这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。编译的指定如下:

    $ gcc -S -I./inc test.c -o test.s

    上述命令中-S让编译器在编译之后停止,不进行后续过程。编译过程完成后,将生成程序的汇编代码test.s,这也是文本文件,内容如下:

    // test.c汇编之后的结果test.s
        .file   "test.c"
        .section    .rodata
    .LC0:
        .string "a=%d, b=%d, a+b=%d
    "
        .text
        .globl  main
        .type   main, @function
    main:
    .LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        andl    $-16, %esp
        subl    $32, %esp
        movl    $2, 20(%esp)
        movl    $3, 24(%esp)
        movl    24(%esp), %eax
        movl    %eax, 4(%esp)
        movl    20(%esp), %eax
        movl    %eax, (%esp)
        call    add 
        movl    %eax, 28(%esp)
        movl    28(%esp), %eax
        movl    %eax, 12(%esp)
        movl    24(%esp), %eax
        movl    %eax, 8(%esp)
        movl    20(%esp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret 
        .cfi_endproc
    .LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
        .section    .note.GNU-stack,"",@progbits

    请不要问我上述代码是什么意思!-_-

    3.汇编(Assemble)

    汇编过程将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件,是二进制格式。gcc汇编过程通过as命令完成:

    $ as test.s -o test.o

    等价于:

    gcc -c test.s -o test.o

    这一步会为每一个源文件产生一个目标文件。因此mymath.c也需要产生一个mymath.o文件

    4.链接(Linking)

    链接过程将多个目标文以及所需的库文件(.so等)链接成最终的可执行文件(executable file)

    命令大致如下:

    $ ld -o test.out test.o inc/mymath.o ...libraries...

    结语

    经过以上分析,我们发现编译过程并不像想象的那么简单,而是要经过预处理、编译、汇编、链接。尽管我们平时使用gcc命令的时候没有关心中间结果,但每次程序的编译都少不了这几个步骤。也不用为上述繁琐过程而烦恼,因为你仍然可以:

    $ gcc hello.c # 编译
    $ ./a.out # 执行

    参考文献

    1.https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html
    2.http://www.trilithium.com/johan/2005/08/linux-gate/
    3.https://gcc.gnu.org/onlinedocs/gccint/Collect2.html

    转载链接:http://www.cnblogs.com/CarpenterLee/p/5994681.html

  • 相关阅读:
    2020年. NET Core面试题
    java Context namespace element 'component-scan' and its parser class ComponentScanBeanDefinitionParser are only available on JDK 1.5 and higher 解决方法
    vue 淡入淡出组件
    java http的get、post、post json参数的方法
    vue 父子组件通讯案例
    Vue 生产环境解决跨域问题
    npm run ERR! code ELIFECYCLE
    Android Studio 生成apk 出现 :error_prone_annotations.jar (com.google.errorprone:error) 错误
    记忆解析者芜青【总集】
    LwIP应用开发笔记之十:LwIP带操作系统基本移植
  • 原文地址:https://www.cnblogs.com/-colin/p/7543382.html
Copyright © 2011-2022 走看看