程序环境和预处理
从源代码到可执行文件的过程
多个源文件分别进行编译生成目标文件(.obj)—->由连接器和链接库进行链接—–>形成可执行文件
预处理到编译到汇编(Linux)
预处理选项 gcc -E test.c -o test.i
预处理 完成之后就停下来,预处理 之后产 生的结果都放在test.i 文件中。
所做的事情包括头文件的包含,define定义符号替换,注释删除
编译 选项 gcc -S test.i
生成汇编代码放在 test.s 文件,并进行语法分析,词法分析,语义分析,符号汇总。
汇编 gcc -c test.s
汇编完成之后就停下来,结果保存在test.o中,汇编代码转换成二进制指令。
test.o 在linux下是ELF文件,在windows下是PE
nm test.o 查看符号表
链接
进行的工作
- 合并符号表,符号重定位,查找链接错误(能否找到对应的符号)
- 合并段表,elf形式存储,相对应的段进行合并
预处理详解
预定义符号
__FILE__ // 进行编译的源文件,使用%s输出
__LINE__ // 文件当前的行号
___DATE___ // 文件被编译的日期_
__TIME__ // 文件被编译的时间
___STDC___ // 如果编译器器遵循 ANSI C ,其值为 1 ,否则未定
#define
可以定义符号和宏,区别就是宏有参数替换
因为定义宏很容易出现优先级错误,所以不要吝啬()来确定优先级
#define MAX 1000 //符号
#define PRINT(NUM) printf("%d",NUM) //宏
PRINT(a);
#define DOUBLE(x) (x) +(x)
printf("%d",10*DOUBLE(2+3)) //55
#define DOUBLE(x) ((x) + (x)) //这样定义就不会错了
参数插入到字符串串
#define PRINT(x,format) printf("the value of " #x "is" format,x)
PRINT(a,10);
#define CAT(x,y) x##y
int hello1 = 100;
print("%d",CAT(hello,1)); //100
#将参数解析为字符串,不进行替换
##拼接参数成为一个字符串
#undef
#undef max //一个名字需要重新定义,先移除
函数和宏的对比
宏的优势
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹’
- 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的
劣势
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增 加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错,并且有副作用(++)
命令行定义
使用场景:在不同的机器上运行相同的程序,可能由于内存限制需要定义不同大小的数组
gcc -DARRAY_SIZE=10 programe.c //-D 选项,能替换代码中的同名参数
条件编译
#define __DEBUG__
#ifdef __DEBUG__
printf("测试");
#endif
#if defined(symbol) //同上,判断是否定义,即使symbol是0
#ifdef symbol
有时候在测试代码中有多处测试需要注释掉,而条件编译能把它们一次全部注释掉。
条件编译后面还可以跟表达式,分支条件编译,嵌套条件编译,若满足条件,则在预编译时将代码呈现,否则删除。
文件包含
#include <stdio.h> //直接去标准位置找,速度快(linux: /usr/include)
#include "add.h" //先去本地找头文件,再去库里找
防止头文件被多次引入,使用条件编译
#ifndef __GAME_H__
#define __GAME_H__
#endif __GAME_H__
#pragma once