在翻译环境中,会将一个C语言.c程序转换成一个可执行.exe程序,那么它是怎么转换的呢?转换的具体过程是什么?
带着这样的疑问,仔细的学习了一下。因为这个过程在Linux系统中狠容易观察,所以演示的时候就用Linux进行演示吧!!!
当然,没有学过Linux的朋友也是可以看的。
目录
一、前言
组成一个程序的每一个源文件都会通过编译分别转换成目标文件
所有的目标文件会由链接器捆绑在一起后形成一个完整的可执行程序
链接器同时会引入标准C函数库中的所有被该程序用到的函数,还可以搜索程序员自己的程序库,将需要的函数链接到程序中
先来简单写一个C语言程序 text.c
#include<stdio.h>
#define M 10
int main()
{
//输出M的值
printf("M = %d\n",M);
printf("file:%s\n",__FILE__);
#ifdef M
printf("Success!\n");
#endif
return 0;
}
下面先简单介绍一下各个指令的作用
选项 | 作用 | 完整指令 |
---|---|---|
-E | 对文件进行翻译,预处理完成后就停下,生成.i文件 | gcc -E text.c -o text.i |
-S | 对文件进行翻译,编译完成后就停下,生成.s文件 | gcc -S text.i -o text.s |
-c | 对文件进行翻译,汇编完成后就停下,生成.o文件 | gcc -c text.s -o text.o |
对文件进行链接,生成可执行文件 | gcc text.o -o text |
指令后面的-o选项可以理解为对gcc生成的文件起一个名字
二、预处理
使用 gcc -E text.c -o text.i 可以生成预编译之后的文件 text.i 文件,-E选项的意思就是对文件进行翻译,执行完预处理之后就停下,这里只能用.c文件进行编译,因为只有.c文件可以生成预编译之后的.i文件
1. define替换
宏替换会将语言内置的预定义符号和自己定义的宏替换成对应的值
1.1 预定义符号
C语言中有一些预定义符号,它们是语言内置的,即已经定义好了,我们可以直接说使用
预定义符号 | 内容 |
---|---|
__FILE__ | 进行编译的源文件 |
__LINE__ | 文件当前的行号 |
__DATE__ | 文件被编译的日期 |
__TIME__ | 文件被编译的时间 |
__STDC__ | 编译器遵循ANSI C时,值为1,否则未定义 |
在输出时,只需要注意使用 %s 还是 %d ,横杠是两个 ' _ '
1.2 #define定义标识符
define可以自己定义一些标识符,如定义常量、给某些关键字起别名、给某些操作起别名等
在定义时,后面不需要加分号
格式为:#define name stuff
//MAX的值定义为100
#define MAX 100
//给register关键字起一个别名reg
#define reg register
//给for( ; ; )操作起一个别名do_forever
#define do_forever for( ; ; )
//case语句后边自动加上break;
#define CASE break;case
//如果stuff太长,则可以多行写,但除了最后一行,其余每行最后都要加一个反斜杠(续行符)
#define PRINT printf("file:%s\t time:%d\n \
",__FILE__,__TIME)
1.3 #define定义宏
define还可以把参数替换到文本中,这种实现称为宏或定义宏
格式为:#define name(parament-list) stuff
parament-list为符号表,可能出现在stuff中,它的括号必须和name紧邻,否则可能就当做stuff了
#define POW(a) a*a
int main()
{
int a = 9;
int x = POW(a); //预处理之后就会将POW(a)替换为a*a
printf("%d", x);
return 0;
}
这个程序运行之后就会输出81
但是这个宏存在一个问题,请看下面的代码
#define POW(a) a*a
int main()
{
int a = 9;
int x = POW(a + 1);
printf("%d", x);
return 0;
}
括号中换成 a + 1 ,我们想计算 a + 1 的平方,但是运行代码之后输出的却是19
这是因为在进行宏替换时,将POW(a + 1)替换成了a+1*a+1,所以输出了19,我们发现是括号的问题,所以就需要给符号加上括号,(a)*(a)
但是还是会出现一个问题,比如下面这个代码
#define DOUBLE(a) (a)+(a)
int main()
{
int a = 9;
int x = 10 * DOUBLE(a);
printf("%d", x);
return 0;
}
我们想计算一下x,理论上我们想要计算的是10乘两倍的a,但是运行结果却是99
同样的一类问题,在进行替换时,变成了10*(9)+(9),所以还需要再加一个括号
写成 #define DOUBLE(x) ( ( x ) + ( x ) )
2. 条件编译
保留满足条件的语句块,去除不满足条件的语句块
常见的条件编译语句:
1.单个的条件编译 3.判断是否定义
#if 常量表达式 #if define(标识符名或变量名)
…… ……
#endif #endif
2.多分支条件编译 #ifdef 标识符名或变量名
#if 常量表达式 ……
…… #endif
#elif 常量表达式 (相反就是 if !define 和 ifndef )
……
#else
……
#endif 可以嵌套定义
3. 头文件包含
我们在编写代码时,一定会引用一些头文件,在预处理时,编译器会先删除这个#include指令,然后将头文件中的内容替换到该文件。
3.1 包含方式
本地文件包含
#include "text.h"
查找方式:先在原文件所在目录下查找,若没有找到就回想查找库函数头文件一样在标准位置查找头文件,如果还找不到就提示编译错误
库文件包含
#include <stdio.h>
查找方式:直接到标准路径下查找,如果没找到就提示编译错误
3.2 嵌套文件包含
在完成一项工程时,多人都需要引用同一个头文件,在合并代码时很容易造成头文件重复包含,包含几次,文件就会被编译几次,造成代码冗余,所以需要进行处理
处理方法:条件编译
1.头文件写成 2.头文件第一行写
#ifndef __TEST_H__ #pragma once
#define __TEST_H__
……
#endif
这样就可以避免头文件的重复包含
4. 删除注释
预编译时,会将文件中的注释删除
5.效果演示
define替换:删除了define指令,并对其定义的内容进行了替换
条件编译:保留了满足条件的语句块,并删除了条件编译指令
头文件包含:预编译之后的.i文件代码达到800多行,其中很大部分都是头文件的内容
删除注释:删除了原文件中的注释内容
三、编译
使用 gcc -S text.i -o text.s 可以生成编译之后的文件 text.s 文件,-S选项的意思就是对文件进行翻译,执行完编译之后就停下,这里既可以用.c文件进行编译,也可以用.i文件,因为这两个文件都可以生成编译之后的.s文件,但是使用.c文件会再预处理一次,所以用.i文件比较好
1. 编译的作用
编译的过程中,编译器会对代码进行语法分析、词法分析、语义分析、符号汇总,检查代码的规范性,将C语言代码翻译成汇编代码
2. 效果演示
下面就是编译过后的.s文件内容,都是一些汇编代码
1 .file "text.c"
2 .section .rodata
3 .LC0:
4 .string "M = %d\n"
5 .LC1:
6 .string "text.c"
7 .LC2:
8 .string "file:%s\n"
9 .LC3:
10 .string "Success!"
11 .text
12 .globl main
13 .type main, @function
14 main:
15 .LFB0:
16 .cfi_startproc
17 pushq %rbp
18 .cfi_def_cfa_offset 16
19 .cfi_offset 6, -16
20 movq %rsp, %rbp
21 .cfi_def_cfa_register 6
22 movl $10, %esi
23 movl $.LC0, %edi
24 movl $0, %eax
25 call printf
26 movl $.LC1, %esi
27 movl $.LC2, %edi
28 movl $0, %eax
29 call printf
30 movl $.LC3, %edi
31 call puts
32 movl $0, %eax
33 popq %rbp
34 .cfi_def_cfa 7, 8
35 ret
36 .cfi_endproc
37 .LFE0:
38 .size main, .-main
39 .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
40 .section .note.GNU-stack,"",@progbits
四、汇编
使用 gcc -c text.s -o text.o 可以生成汇编之后的文件 text.o 文件,-c选项的意思就是对文件进行翻译,执行完汇编之后就停下,这里可以用.c、.i、.s三种都可以
1. 汇编的作用
汇编时会将汇编语言翻译成二进制指令,刚才通过编译进行的符号汇总在汇编时会形成符号表
2. 效果演示
下面就是经过汇编之后的.o目标文件内容,都是一些我们看不懂的二进制指令
1 ^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@p^C^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^M^@^L^@UH<89>å¾
2 ^@^@^@¿^@^@^@^@¸^@^@^@^@è^@^@^@^@¾^@^@^@^@¿^@^@^@^@¸^@^@^@^@è^@^@^@^@¿^@^@^@^@è^@^@^@^@¸^@^@^@^@]ÃM = %d
3 ^@text.c^@file:%s
4 ^@Success!^@^@GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)^@^@^@^@^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@^@^\^@^@^@^@^@^@^@=^@^@^@^@A^N^P<86>^BC^M^Fx^L^G^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@^@^A^@^@^@^D^@ñÿ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^C^@^E^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^G^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^F^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^H^@^@^@ ^R^@^A^@^@^@^@^@^@^@^@^@=^@^@^@^@^@^@^@^M^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^T^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@text.c^@main^@printf^@puts^@^@^@^@^@^@^@^@
5 ^@^@^@^@^@^@^@
6 ^@^@^@^E^@^@^@^@^@^@^@^@^@^@^@^T^@^@^@^@^@^@^@^B^@^@^@
7 ^@^@^@üÿÿÿÿÿÿÿ^Y^@^@^@^@^@^@^@
8 ^@^@^@^E^@^@^@^H^@^@^@^@^@^@^@^^^@^@^@^@^@^@^@
9 ^@^@^@^E^@^@^@^O^@^@^@^@^@^@^@(^@^@^@^@^@^@^@^B^@^@^@
10 ^@^@^@üÿÿÿÿÿÿÿ-^@^@^@^@^@^@^@
11 ^@^@^@^E^@^@^@^X^@^@^@^@^@^@^@2^@^@^@^@^@^@^@^B^@^@^@^K^@^@^@üÿÿÿÿÿÿÿ ^@^@^@^@^@^@^@^B^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@^@.symtab^@.strtab^@.shstrtab^@.rela.text^@.data^@.bss^@.rodata^@.comment^@.note.GNU-sta ck^@.rela.eh_frame^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@^@^A^@^@^@^F^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@@^@^@^@^@^@^@^@=^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^[^@^@^@^D^@^@^@@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@H^B^@^@^@^@^@^@¨^@^@^@^@^@^@^@
12 ^@^@^@^A^@^@^@^H^@^@^@^@^@^@^@^X^@^@^@^@^@^@^@&^@^@^@^A^@^@^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@}^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@,^@^@^@^H^@^@^@^C^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@}^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@1^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@}^@^@^@^@^@^@^@!^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@9^@^@^@^A^@^@^@0^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@<9e>^@^@^@^@^@^@^@.^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@B^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@Ì^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@W^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@Ð^@^@^@^@^@^@^@8^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@R^@^@^@^D^@^@^@@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@ð^B^@^@^@^@^@^@^X^@^@^@^@^@^@^@
13 ^@^@^@^H^@^@^@^H^@^@^@^@^@^@^@^X^@^@^@^@^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^H^A^@^@^@^@^@^@ ^A^@^@^@^@^@^@^K^@^@^@ ^@^@^@^H^@^@^@^@^@^@^@^X^@^@^@^@^@^@^@ ^@^@^@^C^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@(^B^@^@^@^@^@^@^Y^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^Q^@^@^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^H^C^@^@^@^@^@^@a^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@
五、链接
使用 gcc text.o -o text 可以生成预链接之后的文件 text 可执行文件,不需要添加选项
1. 链接的作用
编译过后,生成了很多目标文件,链接器会将符号表进行合并,生成最终的可执行程序
2. 效果演示
链接过后运行生成的可执行程序就可以看到结果了
M = 10
file:text.c
Success!
好了,就介绍到这里,有问题的地方欢迎评论区留言,如果感觉有帮助,还请三连支持!!!