C语言编译与预处理
风&646 人气:0程序的翻译环境和执行环境
在ANSIC的任何一种实现中,存在两个不同的环境:翻译环境和执行环境
翻译环境:源代码被转换为可执行的机器指令。
执行环境:实际执行代码。
1.翻译环境
- 组成一个程序的每个源文件通过编译分别转换成目标文件(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将齐需要的函数也链接到程序中。
2.运行环境
程序的执行环境:
1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2.程序的执行便开始。接着便调用main函数。
3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程中一种保留它们的值。
4.终止程序。正常终止main函数,也有可能是意外终止。
预处理详解
预定义符号
#define
#define 定义标识符
语法:
#define name stuff
#define MAX 100 #define REG register //为register创建一个简短的名字 #define do_forever for(;;) //用符号来替换一种实现 #define PRINT printf("file:%s\tline:%d\tdate:%s\time:%s\n", \ __FILE__,__LINE__,__DATE__,__TIME__) //如果定义的stuff过长,可以分成几行来写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
在define定义标识符的时候,最后不要加(;)号。
#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
宏的声明方式:
#define name ( parament - list ) stuff
其中的parament - list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
参数列表的左括号必须与name紧紧相邻。若两者之间存在空白,参数列表就会被解释为stuff的一部分。
#define SQUARE( x ) x * x
#define替换规则
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们将首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值替换。
3.最后,再一次对结果文件进行扫描,看看是否还包含任何由#define定义的符号。如果是,就重复上述的过程。
注意:
1.宏参数和#define定义中可以出现其它#define定义的变量。但是对于宏,不能出现递归。
2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#和##
如何把参数插入到字符串中?
#include<stdio.h> #define PRINT(n) printf("the value of "#n" is %d\n",n) int main() { int a = 7; PRINT(a); int b = 5; PRINT(b); return 0; }
##的作用:
带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能会出现危险,导致不可预知的后果。副作用就是表达式求值的时候出现的永久性效果。
x+1;//不带有副作用 x++;//带有副作用
举一个例子:
宏和函数对比
宏通常被应用于执行简单的运算。例如在找出两个数中的较大一个。
#define MAX(a, b) ((a)>(b)?(a):(b))
为什么不用函数来完成这个任务呢?有其下原因:
1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2.函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。而这个宏可以适用于整形、长整型、浮点型等类型。宏是类型无关的。
宏和函数的对比:
命名约定
一般来讲函数和宏的使用语法非常相似。所以语法本身没有办法帮我们区分二者。
所以一般是:把宏名全部大写,函数名不要全部大写。
#undef
这条指令用于移除一个宏定义。
#undef NAME //如果现存的一个名字需要被重定义,那么它的旧名字首先要被移除
例如:
命令行定义
许多C的编译器提供了一种功能,允许程序在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性就比较有用。(若某个程序中声明了一个某长度的数组,若机器内存有限,我们需要一个很小的数组,若机器内存大一些,我们就需要一个大一点的数组)。
条件编译
在编译一个程序的时候如果要将一条语句或者一组语句编译或者放弃是很方便的。可以用条件编译指令。
常见的条件编译指令:
文件包含
#include指令可以使另外一个文件被编译。
这种替换的方式:预处理先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含几次,那就实际被编译几次。
头文件被包含的方式:
通常一个大型的项目会调用多个程序合并,那么如何避免头文件的重复包含而提高效率呢?
常见的有一下两种解决方法:
1.每个头文件的开头写:
#ifndef __TEST_H__ #define __TEST_H__ //头文件的内容 #endif
2.定义头文件之前包含这一句话:
#pragma once
加载全部内容