程序环境和预处理
- 1.程序环境
- 1.1如何从test.c到test.exe
- 1.2.翻译环境
- 1.2.1翻译过程
- 1.2.2详细过程
- 1.3.运行环境
- 2.预处理
- 2.1 预定义符号
- 2.2 define
- 2.2.1 define定义标识符
- 2.2.2 define定义宏
- 2.2.3 带副作用的宏参数
- 2.2.4 宏和函数的对比
- 2.3 #undef
- 2.4 命令行定义
- 2.5 条件编译
- 2.6 文件包含
- 2.6.1 头文件被包含的方式:
- 2.6.2 嵌套文件包含
1.程序环境
当我们在使用编译器的时候,编译器的背后是如何运行的工作的?
1.1如何从test.c到test.exe
在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码
1.2.翻译环境
1.2.1翻译过程
1>组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
2>每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
3>链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中
1.2.2详细过程
1.翻译环境
1.3.运行环境
程序执行的过程:
1>程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2>程序的执行便开始。接着便调用main函数。
3>开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4> 终止程序。正常终止main函数;也有可能是意外终止
2.预处理
2.1 预定义符号
FILE //进行编译的源文件
LINE //文件当前的行号
DATE //文件被编译的日期
TIME //文件被编译的时间
STDC //如果编译器遵循ANSI C,其值为1,否则未定义
2.2 define
2.2.1 define定义标识符
defined是预处理命令,他的功能只是替换,不用做任何的运算
不要在define语句后面加 “ ;”,否则有可能会出现语法错误
#define MAX 1000
#define reg register
//为 register这个关键字,创建一个简短的名字
#define do_forever for( ; ; )
//用更形象的符号来替换一种实现
#define CASE break;case
//在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,
//除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,\__DATE__,__TIME__ )
2.2.2 define定义宏
1.相关定义
2.下面观察下面一段代码
这里发现与我们的预想结果不一样,本来以为a+1=6,然后56等于30。但是这里却等于10,这是因为define是预处理命令,只把a+1替换到和b替换到了xy的位置,结果为6+1*5,由于这里存在运算符优先级的问题,所以后面乘法先进行了计算。
3.替换规则
4.#和##
1>#的作用
观察这段代码,发现有许多重复的地方,希望可以通过一个函数那样的方式进行替换,直接带入参数即可,但是函数显然不能满足。这就可以试一试宏定义。
这里需要解释两点,第一点就是字符串具有自动连接功能
第二点就是#的功能:将宏参数a变为对应的字符串
2>##的作用
2.2.3 带副作用的宏参数
2.2.4 宏和函数的对比
宏的缺点:当然和函数相比宏也有劣势的地方:
- 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序
的长度。- 宏是没法调试的
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
2.3 #undef
2.4 命令行定义
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性就有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,此次在命令行编译数组size大小为10;若机器内存够用,需要很大的数组时候,就在命令行编译数组size大小为100等等更大;可根据需求灵活调整)
2.5 条件编译
在编译程序时候,我们可以通过条件编译语法,选择编译这条语句或者放弃编译这条语句
常见的条件编译指令
1.
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__//..
#endif
2.多个分支的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
2.6 文件包含
2.6.1 头文件被包含的方式:
2.6.2 嵌套文件包含
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就
出现了这样一种情况,一个文件包含了多个相同的头文件,这就会导致在预处理的时候进行多次展开该头文件,造成代码冗余,编译更加复杂,那么该如何解决这个问题呢?如何确保该文件只被编译一次呢?--------条件编译.