BIT-0-程序环境和预处理
本章重点:
- 程序的翻译环境
- 程序的执行环境
- 详解:C语言程序的编译+链接
- 预定义符号介绍
- 预处理指令 #define
- 宏和函数的对比
- 预处理操作符#和##的介绍
- 命令定义
- 预处理指令 #include
- 预处理指令 #undef
- 条件编译
正文开始
- 预编译(预处理)
- 作用
- 宏替换:将源程序中的宏定义进行替换。例如,在C/C++ 中,如果有 #define PI 3.14159 ,预编译阶段会把程序中所有的 PI 替换为 3.14159 。
- 文件包含处理:处理 #include 指令,将指定的头文件内容插入到源文件中。这使得在编写程序时可以方便地使用标准库函数或者自定义的头文件中的函数声明、变量声明等内容。
- 条件编译:根据 #if 、 #ifdef 、 #ifndef 、 #else 、 #elif 和 #endif 等预处理指令,有选择地编译程序中的部分代码。例如,可以根据不同的平台定义不同的宏,在预编译阶段决定编译哪些与平台相关的代码段。
- 编译
- 作用
- 词法分析:将源程序的字符流分解成一个个的单词(例如关键字、标识符、常量、运算符等)。
- 语法分析:依据源语言的语法规则,对单词流进行分析,构建出对应的语法树。这一过程用于检查源程序的语法结构是否正确。
- 语义分析:在语法分析的基础上,进一步检查程序的语义是否正确,例如检查类型匹配、变量是否定义等。同时,编译阶段会将源程序转换为中间表示形式(如汇编语言或者某种中间代码,在一些现代编译器中,中间代码是一种与机器无关的代码表示形式)。
- 代码优化:对中间表示形式的代码进行优化,以提高程序的性能。优化的内容可能包括常量折叠(例如将 2 + 3 直接计算为 5 )、公共子表达式消除、循环优化等。
- 目标代码生成:将优化后的中间表示形式转换为目标机器(如特定的CPU架构)的汇编代码或者机器码。
- 汇编
- 作用
- 汇编指令生成:将编译生成的汇编代码转换为目标机器的机器指令。汇编代码是一种与机器指令有直接对应关系的符号表示形式,汇编器将这些符号表示的指令转换为二进制的机器指令,并将其组织成可重定位的目标文件(在类Unix系统中通常是 .o 文件)。
- 符号处理:处理汇编代码中的符号(如变量名、函数名等),将其与对应的内存地址或偏移量相关联。这些符号信息在后续的链接过程中非常重要。
- 链接
- 作用
- 合并目标文件:将多个编译和汇编后得到的目标文件(以及可能的库文件)合并成一个可执行文件或者库文件。例如,在一个大型项目中,不同的源文件分别编译成目标文件后,通过链接器将它们组合在一起。
- 符号解析:解析目标文件之间的符号引用关系。当一个目标文件中的函数或者变量引用了另一个目标文件中的符号时,链接器会在合并目标文件的过程中确定这些符号的实际地址,并进行正确的引用填充。
- 库链接:将程序所需的库文件(静态库或动态库)与目标文件进行链接。如果是静态库,库中的代码会被复制到最终的可执行文件中;如果是动态库,则在运行时动态加载库中的代码。
- 地址重定位:由于目标文件中的地址通常是相对地址或者是可重定位的地址,链接器会对这些地址进行调整,使得程序在内存中的布局正确,确保程序在运行时能够正确地访问数据和代码。
未选择文件
程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代 码。
详解编译+链接
翻译环境
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库, 将其需要的函数也链接到程序中。
编译本身也分为几个阶段:
看代码: sum.c
int g_val = 2016;
void print(const char *str)
{
printf("%s\n", str);
}
test.c
#include <stdio.h>
int main()
{
extern void print(char *str);
extern int g_val;
printf("%d\n", g_val);
print("hello bit.\n");
return 0;
}
如何查看编译期间的每一步发生了什么呢?
test.c
#include <stdio.h>
int main()
{
int i = 0;
for(i=0; i<10; i++)
{
printf("%d ", i);
}
return 0;
}
- 预处理 选项 gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件 中。
- 编译 选项 gcc -S test.c 编译完成之后就停下来,结果保存在test.s中。
- 汇编 gcc -c test.c 汇编完成之后就停下来,结果保存在test.o中。
VIM学习资料
[简明VIM练级攻略:](简明 Vim 练级攻略 | 酷 壳 - CoolShell
运行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须 由手工安排,也可能是通过可执行代码置入只读内存来完成
- 程序的执行便开始。接着便调用main函数。
- . 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同 时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- . 终止程序。正常终止main函数;也有可能是意外终止。
注: 介绍一本书《程序员的自我修养》
预处理详解
预定义符号
_FILE__
__LINE__
__DATE__
__TIME__
__STDC__
//进行编译的源文件
//文件当前的行号
//文件被编译的日期
//文件被编译的时间
//如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号都是语言内置的。 举个栗子:
#include<stdio.h>
#ifdef _STDF_
#define STDK_STR "define"
#else
#define STDK_STR "not define"
#endif
int main() {
printf("file:%s line:%d\n", __FILE__, __LINE__);
printf("__FILE__%s\n", STDK_STR);
}
#define
#define 定义标识符
语法:
#define name stuff
举个例子:
#define MAX 1000
#define reg register