目录
一 程序的翻译环境和执行环境
二 详解编译+链接
2.1 翻译环境
2.2 编译本身也分为几个阶段
2.3 运行环境
三 预处理详解
3.1 预定义符号
3.2 #define
3.2.1 #define 定义标识符
3.2.2 #define定义宏
3.2.3 #define 替换规则
3.2.4 #和##
3.2.5 带副作用的宏参数
3.2.6 宏和函数对比
3.2.7 命名约定
3.3 #undef
3.4 命令行定义
3.5 条件编译
3.6 文件包含
3.6.1 头文件被包含的方式
3.6.2 嵌套文件包含
励志小模块
不要太在意过去,下次做好就行。
重点:程序的翻译环境、程序的执行环境、详解:C语言程序的编译+链接、预定义符号介绍 、预处理指令 #define 、宏和函数的对比 、预处理操作符#和##的介绍 、命令定义 、预处理指令 #include 、预处理指令 #undef 、条件编译
不同的编译器,对于缓冲区实现的方式不同。
翻译环境分成两个部分:编译和链接;(C语言源代码通过编译生成目标文件(.obj/.o)(多个源文件单独通过编译器生成各自的目标文件),目标文件加上链接库通过链接器 链接成为可执行程序(.exe))
编译过程:预编译、编译、汇编
预编译(预处理)——编译——汇编 (预编译也叫作预处理)
预处理之后产生的结果都放在test.i文件中,编译完成之后产生的结果保存在test.s中,汇编完成之后产生的结果保存在test.o或者是test.obj(在linux中是.o;Windows中是.obj)。
预编译:(1)进行头文件的展开(2)删除注释(3)#define定义的符号替换(例如:#define Max 100,在预编译结束之后,Max会被替换为100,并把#define这一行给删掉)总而言之,就是进行一些文本操作。
编译(把C语言代码转换成汇编代码): (1)语法分析(2)词法分析(3)语义分析(4)符号汇总(全局符号:例如:函数名、main等)
汇编(把汇编代码转换成二进制的指令):(1)形成符号表(全局符号给一个地址,所有全局符号+地址形成符号表)
目标文件(.o)加上链接库通过链接器 链接成为可执行程序(.exe))
linux中.o目标文件以及可执行文件的文件格式是elf
链接(编译之后生成.o文件,加上链接库通过链接器进行链接):(1)合并段表(2)符号表(不同文件的符号表)的合并以及重定义
链接的时候,多个目标文件进行链接的时候会通过符号表,查看来自外部的符号是否真实存在
这里讲述的内容都是在预处理阶段
__FILE__ // 进行编译的源文件__LINE__ // 文件当前的行号__DATE__ // 文件被编译的日期__TIME__ // 文件被编译的时间__STDC__ // 如果编译器遵循 ANSI C ,其值为 1 ,否则未定义 (这个并不是所有的编译器都支持)
例子:
#include
int main()
{
printf("%s\n", __FILE__);//编译出来的结果,会显示运行文件的路径
printf("%d\n", __LINE__);//编译出来的结果,显示该行的行数303
printf("%s\n", __DATE__);//显示时间
printf("%s\n", __TIME__);//显示日期
return 0;
}
记录杂志:
#include
#include
#include
int main()
{
int i = 0;
FILE* pf = fopen("log.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
for (i = 0; i < 10; i++)
{
fprintf(pf, "%s %s %s %d %d\n", __DATE__, __TIME__, __FILE__, __LINE__, i);
}
fclose(pf);
pf = NULL;
return 0;
}
语法: #define name stuff (注意:定义完之后,最后不需要分号(;))
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏( macro )或定义宏(define macro )。
#define name( parament - list ) stuff
#include
#define SQUARE(x) x * x
//这也是个替换,用后面的内容替换前面的宏
int main()
{
int a = 5;
printf("%d\n", SQUARE(a + 1));//打印结果为11
return 0;
}
解析:打印结果,我们会理所应当的以为是36,但是结果却是11,因为代入后是a + 1* a+1,所以结果是11,要是想让结果为36,正确的应该是#define SQUARE(x) ((x) * (x))
注意:定义宏,一定要注意括号,记得带上(大部分情况是需要带的,否则会出现优先级问题,导致想要的结果和预期的不一样)
比如上述代码,我们想要的是36,但是结果却是11
每一个部分带上括号,整体也要带上括号
先替换,后计算
使用 # , 把一个宏参数变成对应的字符串
#include
#define PRINT(n) printf("the value of " #n " is %d\n", n)
//#n相当于字符串("a","b"),插入到这个位置,三个都是字符串,//不加引号(;)
int main()
{
int a = 10;
PRINT(a);
int b = 20;
PRINT(b);
printf("the value of " "b" " is %d\n", b);//the value of 是一个字符串,b是一个字符串,is %d\n 是一个字符串
return 0;
}
注意:字符串是有自动连接的特点
## 可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
#include
#define CAT(Class, num) Class##num
int main()
{
int Class1 = 100;
printf("%d\n", CAT(Class, 1));//Class1
return 0;
}
打印结果:100
#include
int main()
{
int a = 2;
int b = 0;
b = ++a;//a = 3; b = 3//这个就有副作用
a = 2;
b = a + 1;//a = 2; b = 3 这个没有副作用
return 0;
}
#include
#define MAX(x, y) ((x) > (y)? (x): (y))
int main()
{
int a = 3;
int b = 5;
int m = 0;
m = MAX(a++, b++);//((a++) > (b++) ? (a++) : (b++))后置++,先使用后++ 3>5 a=4 b=6 m=5 b=7
printf("%d\n", m);
printf("a = %d, b = %d", a, b);
return 0;
}
m=6 a=4 b=7
注意是替换
习惯:把宏名全部大写 函数名不要全部大写
#undef NAME// 如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
#include
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main()
{
#undef MAX
int m = 0;
m = MAX(2, 3);//此时,这个代码无法用MAX这个宏
printf("%d\n", m);
return 0;
}
比如:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
#include
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
#if 0
//1的时候就可以运行,不能是变量
printf("%d\n", i);//此时,这个代码就不运行了
#endif
//这是预处理命令
//其他
}
return 0;
}
1.#if 常量表达式//...#endif// 常量表达式由预处理器求值。2. 多个分支的条件编译#if 常量表达式//...#elif 常量表达式//...#elif 常量表达式//...#else//...#endif3. 判断是否被定义(1)定义#if defined(symbol)//...#endif例如:#define M 100;symbol就是指M(2)定义#ifdef symbol//...#endif(3)没有定义#if !defined(symbol)//...#endif(4)没有定义#ifndef symbol//...#endif4. 嵌套指令#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1 ();#endif#ifdef OPTION2unix_version_option2 ();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2 ();#endif#endif
库文件也可以用""的形式包含,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
如果文件被多次包含,这样就会造成文件内容的重复
解决办法:
(1)
#ifndef __TEST_H__#define __TEST_H__// 头文件的内容#endif //__TEST_H__ 根据文件名改变
(2)
#pragma once
以上两种办法可以避免头文件的重复引入