预处理, 展开头文件/宏替换/去掉注释/条件编译 (test.i)
编译, 检查语法,生成汇编 (test.s)
汇编, 汇编代码转换机器码 (test.o)
链接 链接到一起生成可执行程序 a.out
创建test.c 文件
/*****here some comment*/
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
一,预处理(test.c --> test.i)
展开头文件/宏替换/去掉注释/条件编译, 这一步主要是做一些处理,比如把我们include的一些文件都包含进来。而且把所有的预处理命令都执行掉。比如 我们常见的#define PI 3.14
gcc test.c -E -o test.i
打开test.i看一下:
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 367 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
# 410 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
# 411 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
# 368 "/usr/include/features.h" 2 3 4
# 391 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4
# 10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4
# 392 "/usr/include/features.h" 2 3 4
# 28 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h" 1 3 4
"test.i" 856L, 17105C
二,编译(生成汇编代码)
在这一步会检查语法,进一步生成汇编。
gcc test.c -S-o test.s
.file "test.c"
.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
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
三, 汇编(汇编代码转换机器码)
机器码就是二进制指令码。
gcc test.s -c -o test.o
^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^B^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^M^@
^@UH<89>å¿^@^@^@^@è^@^@^@^@¸^@^@^@^@]Ãhello world^@^@GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609^@^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@^@^\^@^@^@^@^@^@^@^U^@^@^@^@A^N^P<86>^BC^M^FP^L^G^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^D^@ñÿ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^E^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^G^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^F^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^H^@^@^@^R^@^A^@^@^@^@^@^@^@^@^@^U^@^@^@^@^@^@^@^M^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@test.c^@main^@puts^@^@^@^@^@^@^@^E^@^@^@^@^@^@^@
^@^@^@^E^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^B^@^@^@
四,链接
将二进制机器码,转换成二进制语言的可执行程序
gcc test.o -o a.out
链接库
在链接的过程中,有时候会有动态链接库和静态链接库。
静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。
动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,才在应用程序与相应的DLL之间建立链接关系。
将源文件中用到的库函数与汇编生成的目标文件.o合并生成可执行文件。该可执行文件会变大很多,一般是调用自己电脑上的。
静态库和应用程序编译在一起,在任何情况下都能运行,而动态库是动态链接,文件生效时才会调用。
从多个程序出发,就不一样了,比如a程序和b程序都用到了printf函数,使用静态库时,这两个可执行程序都包含了printf函数,所以这时候内存中就包含了两份printf函数;而使用动态库的时候,系统只会加载一份printf函数,当其他函数也要用到printf函数时,只要到加载的printf函数的地址中调用即可,不需要再次加载,所以当多个程序运行时,静态库就明显比动态库更占内存。
函数库中库函数的使用
(1)gcc中编译链接程序默认是使用动态库的,要想静态链接需要显式用-static来强制静态链接。
(2)库函数的使用需要注意3点:第一,包含相应的头文件;第二,调用库函数时注意函数原型;第三,有些库函数链接时需要额外用-lxxx来指定链接;第四,如果是动态库,要注意-L指定动态库的地址。