1.程序的翻译环境
2.程序的执行环境
3.c语言程序的编译+链接
4.预定义符号介绍
5.预处理指令#define
6.宏和函数的对比
7.预处理操作符#和##的介绍
8.命令定义
9.预处理指令#include
10.预处理指令#undef
11.条件编译
程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境,第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令,第二种是执行环境它用于实际执行代码
详解编译+链接
翻译环境
程序的编译过程
1.组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)
2.每个目标文件由连接器(linker)捆绑在一起,形成一个单一而完整的可执行程序
3.连接器同时也会引入标准c函数库中任何被该程序所用到的函数,而且它还可以搜索程序员个人的程序库,将其需要的函数也链接到程序中
通俗讲上面这段话就是 首先翻译环境分为两个阶段,一个叫编译 ,一个叫链接,编译会依赖编译器 编译完了会生成目标文件 目标文件再经过连接器进行链接最终处理生成可执行程序
编译又可以分为几个阶段
第一个阶段 预编译
做的事情是
1. #include头文件的包含,把头文件的内容全部都包含放到.c文件里面去
2.删除注释,使用空格来替换注释
3.将#define定义的符号 全部换成所对应的值
总归一句话预处理做的事情叫做文本操作
test.c文件经过编译器处理会生成一个test.i的文件
第二个阶段 编译
做的事情是
编译是把test.i翻译成test.s把c语言代码翻译成了汇编代码
1.语法分析,看语法有没有错误
2.词法分析
3.语义分析
4.符号汇总 比如全局变量 函数 main g_val
其实就是把c语言代码转换为汇编代码
第三个阶段 汇编
将汇编代码转换生成test.o文件 test.o在windows下是test.obj文件也就是目标文件
做的事情是
1.把汇编代码转换成二进制的代码,也称二进制指令 指令就是代码
2.上一个阶段不是进行符号汇总了,在汇编阶段形成符号表
符号表就是各自的文件中的符号 比如函数名 和全局变量 和它们的地址的组成
那么如何形成符号表
全局变量和函数名
#include<stdio.h>
extern int Add(int x,int y)
int main()
{
return 0;
}
编译阶段不是已经产生符号汇总了
所以汇编阶段就会形成符号表
这里面会产生符号是全局变量ADD和main函数
符号汇总会形成
ADD 0x112233
main 0x223344
将符号和它的地址对应起来就会形成符号表
汇编完了之后生成test.0文件 然后通过连接器生成可执行文件
1.预处理选项 gcc-E test.c -0 test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中
2.编译 选项 gcc-S test.c 编译完成之后就停下来,结果保存在test.s中
3.汇编 gcc-c test.c汇编完成之后就停下来,结果保存在test.o中 .o文件在windows下就是.obj文件
连接发生的事情
1.合并段表
首先目标文件也就是.obj文件它会有自己的格式 会自己分成几个段
将多个目标文件连接在一起 对应段的数据合并在一起,这就是合并段表最终生成一个可执行程序
2.符号表的合并和符号表的重定位
将各自目标文件的符号表合并成为一个符号表
//符号表合并如果符号名相同肯定会用地址有效的符号表
整个翻译环境所做的事情完成 生成可执行程序
运行环境
程序执行的过程
1.程序必须载入内存中,在有操作系统的环境中:一般这个由操作系统完成,在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
2.程序的执行便开始,接着便调用main函数
3.开始执行程序代码,这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值
4.终止程序,正常终止main函数,也有可能是意外终止
预编译阶段也叫预处理阶段
预处理详解
预定义符号
- FILE 文件名符号
- LINE 行号
- DATE 获取日期
- TIME 获取时间
- STDC 果编译器遵循ANSI C 其值为1 否则未定义
这些预定义符号都是语言内置的
int main()
{
printf("%s\n", __FILE__);//文件名符号 d:\桌面\c\date11_2\test11_2\test11_2\today.c
printf("%d\n", __LINE__);//行号 101查看 你在多少行
printf("%s\n", __DATE__);//获取日期 Nov 2 2021
printf("%s\n", __TIME__);//获取时间 17:39:36
printf("%s\n", __STDC__);//如果编译器遵循ANSI C 其值为1 否则未定义
//写日志文件
int i = 0;
int arr[10] = {
0 };
FILE* pf = fopen("log.txt", "w");
for (i = 0; i < 10; i++)
{
arr[i] = i;
fprintf(pf, "file:%s line:%d data:%s time:%s i=%d\n",
__FILE__,__LINE__,__DATE__,__TIME__,i);
}
fclose(pf);
pf = NULL;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
预处理指令都有哪些
#define
#include
#if
#end
#end if
#pragma pack(4)
#pragma
#ifdef
#line
以#开头的都叫预处理指令
#define 预处理指令
#define 定义标识符
语法 #define name stuff
实例
#define MAX 1000
#define reg register //为register这个关键字,创建一个简短的名字
#define do_forever for(;;)//用更形象的符号来替换一种实现
#define CASE break;case//再写case语句的时候自动把break写上
//如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续航符)
#define DEBUG_PRINT print("file:%s\t line:%d\t \
date:%s\t time:%s\n", \
__FILE__,__LINE__, \
__DATE__,__TIME__)
结论 #define max 1000 后面不要加; 因为容易导致语法错误
define 定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
下面是宏的申明方式:
#define name(parament-list)stuff 其中的parament-list是一个由逗号隔开的符号表他们可能出现在stuff中
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在参数列表就会被解释为stuff的一部分
#define SQUARE(X) ((X)*(X)) //SQUARE就是宏
#define SQUARE(X) ((X)*(X))
这里将5给x 然后后面的替换掉x就是5x5了
牢牢记住这里不是把5传给x而是把5跟x进行了替换,也就是写宏的时候永远
不要忘记括号
int main()
{
int ret = SQUARE(5);
printf("%d\n"