十五.编译过程
file.c,file.h经过预处理器处理成为file.i,再经过编译器(gcc)成为汇编file.s,再经过汇编器(as)成为file.o,最后通过连接器(linker)成为可执行文件。
预处理器:1.处理所有的注释,以空格代替
2.将所有的#define删除,并展开所有定义的宏
3.处理条件编译指令,#if,#ifdef,#elif,#else,#endif
4.处理#include,展开被包含的文件
5.保留编译器需要使用的#pragma指令
指令为:gcc -E file.c -o hello.i,得到预处理器处理完后的文件
编译器 : 对预处理文件进行一系列的词法分析,语法分析,和语义分析,分析结束后进行代码优化,生成相应的汇编代码
指令为:gcc -S file.c -o hello.s
汇编器 :将汇编代码转变成为可执行的指令。
指令为:gcc -c file.s -o hello.o
连接器 :把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接,静态链接在编译期完成,动态链接在运行期完成。
十六.宏定义与使用分析
宏可以定义在代码的任何地方,之后的代码都可以使用。
宏表达式与函数的对比:
1.宏表达式在预编译器被处理(单纯的展开),编译器不知道宏表达式的存在
2.宏表达式用“实参”完全代替形参,不进行任何运算,任何字符都可以替换,应用更广
3. 宏表达式没有任何调用开销
4.宏表达式中不能出现递归定义
个宏比
一个宏比函数强的例子:
#define DIM(array) (sizeof(array)/sizeof(*array))第一个array代表数组
int dim(int array[])
{
return sizeof(array)/sizeof(*array);//第一个array是指针
}
在这个求数组长度的例子中,宏由于是直接替换,所以没问题,但是 数组名做函数参数直接就会退化成为指针,返回值就不正确了(指针所占位数/数组第一个元素的内存位数,32位返回1,64位返回2)。
内置宏:
十七.条件编译使用分析
可以在进行编译的时候指定程序需要使用的宏定义,来进行预处理展开,-D定义宏 -U取消宏
#include 本质是将文件内容嵌入当前文件,所以被包含的文件应当使用#ifndef……#define……#endif,防止重复包含
十八.#error和#line指示字
#error用于生成一个编译错误消息,并停止编译
用法:#error message(message不需要双引号)
#warning类似作用
#line 用于强制指定新的行号和编译文件名,并对源程序的代码重新编号,新的行号从下一行开始。用于指定自己的代码,现在可以用更先进的版本控制以及静态库和动态库来解决多人编码
用法:#line number filename(filename可以省略)
十九.#pragma预处理分析
#pragma是编译器指示字,用于指示编译器完成一些特定的动作。
#pragma 所定义的很多指示字是编译器和操作系统特有的,在不同的编译器间是不可移植的
#pragma: 预处理器会忽略不认识的#pragma指令并删除,并将认识的指令留给编译器来处理
#pragma message:编译时输出消息到编译输出窗口,可用于代码的版本控制,VC特有,GCC将会忽略
内存对齐:
为什么:cpu对内存读取不是连续的,而是分块读取的,块的大小只能是1,2,4,8,16字节
当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣
某些硬件平台只能从规定的地址处取某些特定类型的数据,否则会抛出硬件异常
#pragma pack能够改变编译器的默认对齐方式
struct占用内存大小计算方法:
gcc默认为4个字节对齐
不能使用memcmp函数判断结构体是否相等,因为memcmp是使用内存进行判断,结构体使用前不清零,即使重新赋相同的值,每个内存地址里的值也不一定相同,另外一个原因就是因为对齐,有些内存地址是未定义的。
二十.#和##运算符
#运算符是预处理指令开始符,比如#include<>
#运算符用于在预编译期将宏参数转换为字符串
#include <stdio.h>
#define CONVERS(x) #x
int main()
{
printf("%s\n", CONVERS(Hello world!));
printf("%s\n", CONVERS(100));
printf("%s\n", CONVERS(while));
printf("%s\n", CONVERS(return));
return 0;
}
输出结果:Hello world!
100
while
return
## 用于在预编译期粘连两个符号
#include <stdio.h>
#define NAME(n) name##n
int main()
{
int NAME(1);
int NAME(2);
NAME(1) = 1;
NAME(2) = 2;
printf("%d\n", NAME(1));
printf("%d\n", NAME(2));
return 0;
}
输出:1
2
预处理器把NAME(1)替换成name1,NAME(2)替换成name2
用处:定义结构体类型。
#include <stdio.h>
#define STRUCT(type) typedef struct _tag_##type type;\
struct _tag_##type
STRUCT(Student)
{
char* name;
int id;
};
int main()
{
Student s1;
Student s2;
s1.name = "s1";
s1.id = 0;
s2.name = "s2";
s2.id = 1;
printf("%s\n", s1.name);
printf("%d\n", s1.id);
printf("%s\n", s2.name);
printf("%d\n", s2.id);
return 0;
}
高效整洁的定义结构体,用于高通平台。