【第一步】编辑hello.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 int main()
4 {
5 printf("hello world!\n");
6 return 0;
7 }
【第二步】预处理
预处理阶段需处理任务:
(1)将所有的#define删除,并且展开所有的宏定义。说白了就是字符替换
(2)处理所有的条件编译指令,#ifdef #ifndef #endif等,就是带#的那些
(3)处理#include,将#include指向的文件插入到该行处(将头文件中的内容(源文件之外的文件)插入到源文件中)
(4)删除所有注释
(5)添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪个文件的哪一行
(6)保留#pragma编译器指令,因为编译器需要使用它们。
gcc -E hello.c -o a.i可以生成预处理后的文件。通过查看文件内容和文件大小可以得知a.i讲stdio.h和stdlib.h包含了进来。
【第三步】编译
编译的过程实质上是把高级语言翻译成机器语言的过程,即对a.i做了这些事儿
(1)词法分析,
(2)语法分析
(3)语义分析
(4)优化后生成相应的汇编代码
从 高级语言->汇编语言->机器语言(二进制)
gcc -S hello.c -o a.s可以生成汇编代码
汇编代码如下。
1 .file "hello.c"
2 .section .rodata
3 .LC0:
4 .string "hello world!"
5 .text
6 .globl main
7 .type main, @function
8 main:
9 .LFB0:
10 .cfi_startproc
11 pushl %ebp
12 .cfi_def_cfa_offset 8
13 .cfi_offset 5, -8
14 movl %esp, %ebp
15 .cfi_def_cfa_register 5
16 andl $-16, %esp
17 subl $16, %esp
18 movl $.LC0, (%esp)
19 call puts
20 movl $0, %eax
21 leave
22 .cfi_restore 5
23 .cfi_def_cfa 4, 4
24 ret
25 .cfi_endproc
26 .LFE0:
27 .size main, .-main
28 .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
29 .section .note.GNU-stack,"",@progbits
gcc -c hello.c -o a.o将源文件翻译成二进制文件。类Uinx系统编译的结果生生成.o文件,Windows系统是生成.obj文件。
编译的过程就是把hello.c翻译成二进制文件
【第四步】链接
就像刚才的hello.c它使用到了C标准库的东西“printf”,但是编译过程只是把源文件翻译成二进制而已,这个二进制还不能直接执行,这个时候就需要做一个动作,
将翻译成的二进制与需要用到库绑定在一块。打个比方编译的过程就向你对你老婆说,我要吃雪糕。你只是给你老婆发出了你要吃雪糕的诉求而已,但是雪糕还没有到。
绑定就是说你要吃的雪糕你的老婆已经给你买了,你可以happy。
gcc hello.c -o a可以生成可执行程序。即gcc不带任何参数。ldd就可以看到你的可执行程序依赖的库。
可以看到a.o的大小是1.1k,毕竟他只是把源文件翻译成二进制文件。a却有7k,应该是他多了很多“绳子”吧。在运行的时候这些“绳子”就将对应的库函数“牵过来”。很形象的比喻是不是?哈哈。libc.so.6 中就对咱们用的printf进行了定义。
3、 优化及汇编过程
将汇编代码翻译成机器指令——即生成计算机系统可识别的二进制代码。汇编的过程实际上就是将汇编语言代码翻译成为机器语言的过程。这时候生成的代码实际上并不能直接运行,要经过链接以后才可以运行。
链接过程如下:
gcc——预处理(预编译),编译,汇编,链接
2016年11月08日 17:14:41 阅读数:3122
一,预编译
操作步骤:gcc -E hello.c -o hello.i
主要作用:
处理关于 “#” 的指令
【1】删除#define,展开所有宏定义。例#define portnumber 3333
【2】处理条件预编译 #if, #ifdef, #if, #elif,#endif
【3】处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。
【4】删除所有注释。/**/,//。
【5】添加行号和文件标识符。用于显示调试信息:错误或警告的位置。
【6】保留#pragma编译器指令。(1)设定编译器状态,(2)指示编译器完成一些特定的动作。
二,编译(编译器主要做了什么)
操作步骤:gcc -s hello.c -o hello.s
主要作用:1.扫描(词法分析),2.语法分析,3.语义分析,4.源代码优化(中间语言生成),5.代码生成,目标代码优化。
【1】将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。例array[index] = (index + 4) * (2 + 6);
【2】基于词法分析得到的一系列记号,生成语法树。
【3】由语义分析器完成,指示判断是否合法,并不判断对错。又分静态语义:隐含浮点型到整形的转换,会报warning,
动态语义:在运行时才能确定:例1除以3
【4】中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。
【5】编译器后端主要包括:代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等
目标代码优化器:选择合适的寻址方式,左移右移代替乘除,删除多余指令。
三,汇编
操作步骤:gcc -c hello.c -o hello.o
主要作用:汇编器是将汇编代码转变成可以执行的指令,生成 目标文件。
四,链接
操作步骤:gcc hello.o -o hello
主要作用:通过编译器的5个步骤后,我们获得目标代码,但是里面的各个地址还没有确定,空间还没有分配。
链接过程主要包括:地址和空间的分配,符号决议和重定位。
地址和空间:略
符号决议:也可以说地址绑定,分动态链接和静态链接,
重定位:假设此时又两个文件:A,B。A需要B中的某个函数mov的地址,未链接前将地址置为0,当A与B链接后修改目标地址,完成重定位。
gcc——预处理(预编译),编译,汇编,链接
2016年11月08日 17:14:41 阅读数:3121
一,预编译
操作步骤:gcc -E hello.c -o hello.i
主要作用:
处理关于 “#” 的指令
【1】删除#define,展开所有宏定义。例#define portnumber 3333
【2】处理条件预编译 #if, #ifdef, #if, #elif,#endif
【3】处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。
【4】删除所有注释。/**/,//。
【5】添加行号和文件标识符。用于显示调试信息:错误或警告的位置。
【6】保留#pragma编译器指令。(1)设定编译器状态,(2)指示编译器完成一些特定的动作。
二,编译(编译器主要做了什么)
操作步骤:gcc -s hello.c -o hello.s
主要作用:1.扫描(词法分析),2.语法分析,3.语义分析,4.源代码优化(中间语言生成),5.代码生成,目标代码优化。
【1】将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。例array[index] = (index + 4) * (2 + 6);
【2】基于词法分析得到的一系列记号,生成语法树。
【3】由语义分析器完成,指示判断是否合法,并不判断对错。又分静态语义:隐含浮点型到整形的转换,会报warning,
动态语义:在运行时才能确定:例1除以3
【4】中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。
【5】编译器后端主要包括:代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等
目标代码优化器:选择合适的寻址方式,左移右移代替乘除,删除多余指令。
三,汇编
操作步骤:gcc -c hello.c -o hello.o
主要作用:汇编器是将汇编代码转变成可以执行的指令,生成 目标文件。
四,链接
操作步骤:gcc hello.o -o hello
主要作用:通过编译器的5个步骤后,我们获得目标代码,但是里面的各个地址还没有确定,空间还没有分配。
链接过程主要包括:地址和空间的分配,符号决议和重定位。
地址和空间:略
符号决议:也可以说地址绑定,分动态链接和静态链接,
重定位:假设此时又两个文件:A,B。A需要B中的某个函数mov的地址,未链接前将地址置为0,当A与B链接后修改目标地址,完成重定位。
所谓链接的过程就是指,经过编译后将会生成一个目标文件,这个目标文件可能会调用printf等函数,对于printf函数,它的目标代码在系统的函数库中(一般用户用到的很多函数库都存在于/usr/lib或者/lib中),链接所要做的就是将这些函数库中相应的代码组合到目标文件中去。
链接分为:
静态链接;就是把函数库中包含的目标代码静态添加到可执行文件中。一般静态链接生成的文件比较大。可独立运行。
动态链接;将这些函数库的路径、函数名等信息添加到可执行文件中去,在执行过程中动态加载函数库。需要函数库的支持。
Linux下动态链接库默认后缀名是".so",静态链接库默认后缀名是“.a”。
gcc 编译的四大过程(预处理-编译-汇编-链接 )
我们来编译一个hello world 程序。
#include <stdio.h>
int main(int argc,const char* argv[])
{
printf("hello world!\n");
return 0;
}
1)预处理(Pre-processing)
在该阶段,编译器将C源代码中的包含的头文件如stdio.h编译进来,用户可以使用gcc的选项”-E”进行查看。
用法:#gcc -E main.c -o main.i
作用:将main.c预处理输出main.i文件
[user:test] ls
main.c
[user:test] gcc -E main.c -o main.i
[user:test] ls
main.c main.i
2)编译阶段(Compiling)
第二步进行的是编译阶段,在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
选项 -S
用法:[user]# gcc –S main.i –o main.s
作用:将预处理输出文件main.i汇编成main.s文件。
[user:test] ls
main.c main.i
[user:test] gcc -S main.i -o main.s
[user:test] ls
main.c main.i main.s
3)汇编阶段(Assembling)
汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码.
选项 -c
用法:[user]# gcc –c main.s –o main.o
作用:将汇编输出文件main.s编译输出main.o文件。
[user:test] ls
main.c main.i main.s
[user:test] gcc -c main.s -o main.o
[user:test] ls
main.c main.i main.o main.s
4)链接阶段(Link)
在成功编译之后,就进入了链接阶段。
无选项链接
用法:[user]# gcc main.o –o main.exe
作用:将编译输出文件main.o链接成最终可执行文件main.elf
[user:test] ls
main.c main.i main.o main.s
[user:test] gcc main.o -o main.elf
[user:test] ls
main.c main.elf* main.i main.o main.s