编译总结(一)



  • 编译总结(一)


    一、系统环境

    • CPU:Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz

    • 操作系统:Ubuntu 18.04.2 LTS

    • 内核版本:Linux version 4.18.0-25-generic

    • GNU GCC版本:gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)

      • C standard revision:C11
      • GNU Compiled BY
        • GMP version: 6.1.2
        • MPFR version :4.0.1
        • MPC version : 1.1.0
        • isl version : isl-0.19-GMP
    • GNU 汇编器版本:2.30 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.30

    • 链接器版本:

      • collect2 version:7.4.0
        • gcc一般是collect2,而不是ld,collect2 是ld链接器的一个封装,最终还是调用ld来完成链接工作
        • collect2通过第一次链接程序查看链接器输出文件来查找具有特定名称表明是构造函数的符号,如果找得到则会创建一个新的临时‘.c’文件包含这些符号,然后编译这个文件并第二次链接程序.The program collect2 works by linking the program once and looking through the linker output file for symbols with particular names indicating they are constructor functions. If it finds any, it creates a new temporary ‘.c’ file containing a table of them, compiles it, and links the program a second time including that file.)
      • GNU ld (GNU Binutils for Ubuntu):2.30

    二、GCC编译过程

    2.1 GCC编译过程

    编译过程-简单

    1. 预处理

      • 删除所有的#define,展开所有的宏定义
      • 处理所有的条件预编译指令<#if,#endif,#ifdef,#ifndef,#elif,#else>
      • 处理#include预编译指令,将包含的文件插入到include的位置(递归进行)
      • 删除所有的注释
      • 添加行号和文件名标识(调试时使用)
      • 保留所有的#pragma编译器指令(编译器需要使用这些指令)
      # 单独产生预处理后的文件(本模块假设hello.c是源代码程序,hello.i是hello.c预处理后的文件,hello.s是hello.c编译后的文件,hello.o是hello.c汇编后的文件,hello是hello.c最终的可执行程序)
      
      # 使用gcc命令产生预处理文件
      $ gcc -E hello.c -o hello.i
      
      # 使用cpp命令产生预处理文件
      $ cpp hello.c > hello.i
      
    2. 编译:将预处理完的文件进行一系列的词法分析、语法分析、语义分析、中间代码生成、目标代码生成与优化之后产生相应的汇编代码文件

      • 词法分析:扫描器运行类似于有限状态机的算法将代码的字符序列分割成一系列的记号
      • 语法分析:语法分析器对扫描器产生的记号进行语法分析,从而产生语法树(以表达式为节点的树)
      • 语义分析:语义分析器确定语句的意义(比如两个指针做乘法是没有意义的),编译器只能分析静态语义(在编译时能够确定的语义,通常包括声明和类型的匹配,类型的转换;与之相对的动态语义是在运行时才能确定的语义,例如将0作为除数是一个运行期语义错误)
      # 编译预处理后的文件产生汇编代码文件
      $ gcc -S hello.i -o hello.s
      
      # 编译源文件产生汇编代码文件
      $ gcc -S hello.c -o hello.s
      
      # 现在的gcc编译器将预处理和编译两个步骤合成了一个步骤,使用一个叫cc1的程序来完成这个过程
      $ /usr/lib/gcc/x86_64-linux-gnu/7/cc1 hello.c -o hello.s
      
    3. 汇编:将汇编代码转变成机器可以执行的指令(根据汇编指令和机器指令的对照表一一翻译)

      # 使用as处理汇编文件产生目标文件
      $ as hello.s -o hello.o
      
      # 使用gcc处理汇编文件产生目标文件
      $ gcc -c hello.s -o hello.o
      
      # 使用gcc处理源文件产生目标文件
      $ gcc -c hello.c -o hello.o
      
    4. 链接:将目标文件链接到一起形成可执行文件,主要包括地址和空间分配,符号决议,和重定位等步骤

      • 符号决议:也叫做符号绑定、名称绑定、名称决议等等。从细节上来讲,决议更倾向于静态链接,绑定更倾向与动态链接

      • 重定位:编译一个文件时不知道一个要调用的函数或者需要操作的一个变量的地址,就会把这些调用函数或者操作变量的指令目标地址搁置,等到最后链接的时候由链接器去将这些指令的目标地址修正,这个地址修正的过程也被叫做重定位,每一个需要修正的地方叫做重定位入口

    2.2 实际编译过程

    1. 使用如下样例,包含hello.c和func.c两个源文件(之后也是用这两个文件进行分析)

      /* hello.c:主测试程序,包括全局静态变量,局部静态变量,全局变量,局部变量,基本的函数调用 */
      // export var
      extern int export_func_var;
      
      // global var
      int global_uninit_var;
      int global_init_var_0 = 0;
      int global_init_var_1 = 1;
      
      // const var
      const char *const_string_var = "const string";
      
      // static global var
      static int static_global_uninit_var;
      static int static_global_init_var_0 = 0;
      static int static_global_init_var_1 = 1;
      
      // func header
      void func_call_test(int num);
      
      int main(void){
          // local var
          int local_uninit_var;
          int local_init_var_0 = 0;
          int local_init_var_1 = 1;
      
          // static local var
          static int static_local_uninit_var;
          static int static_local_init_var_0 = 0;
          static int static_local_init_var_1 = 1;
      
          // call func
          func_call_test(8);
      
          // export var op
          export_func_var = export_func_var * 2;
      
          return 0;
      }
      
      /* func.c:包含一个简单的被调用函数和一个全局变量 */
      int export_func_var = 666;
      
      void func_call_test(int num){
          int double_num = num * 2;
      }
      
    2. 使用gcc -v hello.c func.c编译生成可执行文件a.out,产生如下输出(简化版本)

      [delta@delta: code ]$ gcc -v func.c hello.c
      
      # 对func.c的预处理和编译过程
      /usr/lib/gcc/x86_64-linux-gnu/7/cc1 func.c -o /tmp/ccfC6J5E.s
      # 对func.c产生的.s文件汇编产生二进制文件
      as -v --64 -o /tmp/ccF4Bar0.o /tmp/ccfC6J5E.s
      
      # 对hello.c的预处理和编译过程
      /usr/lib/gcc/x86_64-linux-gnu/7/cc1 hello.c -o /tmp/ccfC6J5E.s
      # 对hello.c产生的.s文件汇编产生二进制文件
      as -v --64 -o /tmp/cc7UmhQl.o /tmp/ccfC6J5E.s
      
      # 链接过程
      /usr/lib/gcc/x86_64-linux-gnu/7/collect2 -dynamic-linker ld-linux-x86-64.so.2 Scrt1.o crti.o crtbeginS.o /tmp/ccF4Bar0.o /tmp/cc7UmhQl.o crtendS.o crtn.o
      

 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

Looks like your connection to Dian was lost, please wait while we try to reconnect.