目录
一. 程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码
二. 详解编译+链接
1.exe生成过程
![](https://i-blog.csdnimg.cn/blog_migrate/d616f2b4d08f4e0adc463434fd25d58f.png)
上图即为形成可执行文件的大概过程:首先组成该程序的的每个项目源文件通过编译器的编译后分别转换成目标代码(object code)。然后每个目标文件和链接库由链接器(linker)链接在一起最后形成一个单一而完整的可执行程序。链接库就是标准C函数库中被该程序所用到的函数,另外它还可以包括程序员个人的程序库,将被使用的程序员个人的程序库里的函数。
2.详解编译
编译其实主要包括以上三个阶段。
2.1预处理阶段
以一段具体代码分析,头文件的引用在预处理阶段会被整体引入文件里。而#define定义的内容会被替换(更具体的可以查看博主的上一篇宏的文章)。另外,编译器会将注释全部丢弃。
上述的头文件处理就是源文件包含,文件包含是指,一个源文件可以将另一个源文件的全部内容包含进来。主要包含两种方式:#include “包含文件名” 或#include <包含文件名>。 使用""包含查找策略是先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。而<>则直接从标准路径开始查找,如果找不到就提示编译错误。
#define定义的内容会被替换就是指 宏替换
另外预处理指令还包括条件编译、行控制、抛错、杂注和空指令。
条件编译:在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃,我们可以使用条件编译指令来完成。预处理器根据条件编译指令,有条件地选择源程序代码中的一部分代码进行编译。主要是为了有选择性地执行相应操作,防止宏替换内容(如文件等)的重复包含。
条件编译指令 | 说 明 |
---|---|
#if | 如果条件为真,执行相应操作 |
#elif | 如果前面条件为假,而该条件为真,执行相应操作 |
#else | 如果前面条件均为假,则执行相应操作 |
#endif | 结束相应的条件编译指令 |
#ifdef | 如果该宏已定义,则执行相应操作 |
#ifndef | 如果该宏没有定义,则执行相应操作 |
#define __DEBUG__ 0
#if __DEBUG__
//..(被丢弃,因为条件不满足)
#endif
//多个分支条件编译(和选择结构一样只选择一条语句进入)
#define __DEBUG1__ 0
#define __DEBUG2__ 0
#if __DEBUG1__
//...
#elif __DEBUG2__
//...
#else
//...
#endif
//判断是否被定义
//symbol被定义则进入编译
#if defined(symbol)
#ifdef symbol
//symbol没被定义则进入编译
#if !defined(symbol)
#ifndef symbol
拓展:条件编译解决头文件被重复包含问题
问题引入:
test包括了pro1,pro2两模块,而他们都用了com模块,那么com.h将会被重复引用,我们知道头文件被引用是将其内容全部写入,不但会导致代码变长冗余还会导致编译错误(因为头文件里的内容重复写两次,将很可能导致变量,函数重复定义从而报错)。
问题解决:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
这样在这个头文件第一次被引用时就会定义 __TEST_H__并且编译头文件内容,如果第二次试图重复引入该头文件会因为 __TEST_H__已经被定义而被丢弃。
行控制(#line):
#line line_number "filename"
#ine命令是用于更改_LINE_和__FILE_变量的值.line_number规定了下一行代码的行号。接下来的行号会依次递增。"filename"是重新定义要显示的文件名。
文件名这个参数是可以省略的,可以不改变。
抛错#error
抛错指令用于在预处理期间发出一个诊断信息停止转换。
#ifndef UNIX
#error This val is ilegal.
#endif
杂注
杂注指令可以向C实现传递信息实现对程序的某些方面进行控制。以“#”开始,跟着“pragma”,后面是其他预处理记号。下面这个杂注用于指示C实现将修改结构体的默认对齐数为括号里的数字。
# pragma pack(1)
空指令
空指令“#”自成一行,没有特别的效果。
2.2编译阶段
在这个阶段编译器将C语言代码转换成汇编代码。还会进行四项操作,分别是语法分析、词法分析、语义分析、符号汇总。其中符号汇总主要是将函数名等符号摘取出来。
2.3汇编阶段
编译器把汇编指令转化为二级制指令,还会将前一阶段形成的符号汇总放在一起制成一张符号表。
3.详解链接
各个文件经过编译三个阶段将会形成.obj的文件,然后这些文件将会被链接在一起形成.exe文件。
合并符号表的时候就会发现各种链接问题。如调用函数未定义等。
三.运行环境详解
程序执行的过程:
1. 程序载入到内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序
的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。