gcc 生成a.out 的过程

编译-build过程

hello.c + hello.hhello.c + hello.hhello.ihello.ihello.shello.shello.ohello.ohellohello预处理文本替换宏展开删除注释...编译编译程序:词法分析语法分析语义分析语义优化生成相应的汇编代码文件汇编汇编程序:指令的翻译生成可重定向目标文件链接链接程序:将hello.o、外部库进行链接生成可执行文件

四个步骤:

  • 预处理 preprocessing
  • 编译 compilation
  • 汇编 assembly
  • 链接 linking

下面以 hello world 程序进行分析

#include <stdio.h>

void main(){
        printf("hello world"); // 打印出hello world
}

预处理

预处理过程主要是处理那些源文件和头文件中以#开头的命令,比如 #include、#define、#ifdef 等。预处理的规则一般如下

  • 将所有的#define删除,并展开所有的宏定义
  • 处理所有条件编译命令,比如 #if、#ifdef、#elif、#else、#endif 等。
  • 处理#include命令,将被包含文件的内容插入到该命令所在的位置,这与复制粘贴的效果一样。注意,这个过程是递归进行的,也就是说被包含的文件可能还会包含其他的文件
  • 删除所有的注释//和/* … */。
  • 添加行号和文件名标识,便于在调试和出错时给出具体的代码位置
  • 保留所有的#pragma命令,因为编译器需要使用它们。

预处理的结果是生成.i文件 , .i文件也是包含C语言代码的源文件,只不过所有的宏已经被展开,所有包含的文件已经被插入到当前文件中。当你无法判断宏定义是否正确,或者文件包含是否有效时,可以查看.i文件来确定问题。

$gcc -E demo.c -o demo.i

# 1 "test.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "test.c"
# 1 "/usr/include/stdio.h" 1 3 4      // 第一行执行include
# 27 "/usr/include/stdio.h" 3 4

/**省略一部分,省略了部分内容,包括stdio.h中的一些声明及定义**/

# 2 "test.c" 2     // 第二行回到test.c 文件main函数


void main(){
 printf("hello world");    // 没有了注释,表明删除了注释
 printf("a=%d",10);
}

编译

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

gcc -S test.i -o test.s 只做编译 不做汇编和链接
或者
gcc -S demo.c -o demo.s

    .file    "test.c"
    .section    .rodata
.LC0:
    .string    "hello world"
.LC1:
    .string    "a=%d"
    .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
    movl    $.LC0, %edi
    call    puts
    movl    $10, %esi
    movl    $.LC1, %edi
    movl    $0, %eax
    call    printf
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
    .section    .note.GNU-stack,"",@progbits

汇编

汇编的过程就是将汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令

汇编的结果是产生目标文件,在 GCC 下的后缀为.o,在 Visual Studio 下的后缀为.obj

gcc -c -o test.c test.o 编译和汇编, 不做链接
hexdump test.o 或者 od -tx1 -tc -Ax test.o

[root@iz2zecj7a5r32f2axsctb9z extern]# hexdump test.o
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010 0001 003e 0001 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0300 0000 0000 0000
0000030 0000 0000 0040 0000 0000 0040 000d 000c
0000040 4855 e589 00bf 0000 e800 0000 0000 0abe
0000050 0000 bf00 0000 0000 00b8 0000 e800 0000
0000060 0000 c35d 6568 6c6c 206f 6f77 6c72 0064
0000070 3d61 6425 0000 4347 3a43 2820 4e47 2955
0000080 3420 382e 352e 3220 3130 3035 3236 2033
0000090 5228 6465 4820 7461 3420 382e 352e 342d
00000a0 2934 0000 0000 0000 0014 0000 0000 0000

链接

目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,程序不能执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。

gcc -o test test.o
gcc -v -o test test.o 查看链接过程

[root@iz2zecj7a5r32f2axsctb9z extern]# gcc -v -o test test.o
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
目标:x86_64-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
线程模型:posix
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) 
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'test' '-mtune=generic' '-march=x86-64'
 /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o test /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. test.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o

可以看到动态链接了一些文件

-dynamic-linker /lib64/ld-linux-x86-64.so.2 -o test /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. test.o -lgcc –as-needed -lgcc_s –no-as-needed -lc -lgcc –as-needed -lgcc_s –no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o

crt1.o、crti.o 、crtbegin.o、crtend.o 、crtn.o 是gcc加入的系统标准启动文件,对于一般应用程序,这些启动是必需的
-lc 链接libc库文件,其中libc库文件中就实现了printf等函数

链接分为动态链接和静态链接

  • 动态链接: 动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。 动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。

    在window中动态链接库文件为.dll 在linux中为.so
    默认使用动态链接库

  • 静态链接:静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行,不过静态链接生成的程序体积较大。

    在linux中静态链接库为.a文件 window中静态链接库为.lib文件

文档更新时间: 2021-03-08 17:59   作者:周国强