程序环境和预处理
- 程序的翻译环境
- 程序的执行环境
- 预处理详解
- 宏和函数对比
- 条件编译*
程序的翻译环境
-
组成一个程序的每个源文件通过编译过程分别转换成目标代码
-
每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序
-
链接器同时会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中
编译本身也分为几个阶段
程序的执行环境 -
程序必须载入内存中,在有操作系统的环境中:一般由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可通过可执行代码置入只读内存来完成
-
程序的执行开始,接着调用main函数
-
开始执行程序代码,这个时候程序将使用一个运行时的堆栈,存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值
-
终止程序,正常终止main函数,也有可能意外终止
预处理详解
#define定义标识符
#define Max 1000
#define定义宏
#define DOUBLE(x) ((x)+(x))
- 对数值表达式进行求值定义的宏应适当用括号,避免在使用宏时由于参数中的操作符或邻近操作符之际不可预料的相互作用
- 带副作用的宏参数
当宏参数在宏的定义中超过一次的时候,如果参数带副作用,那么在使用宏时就可能出现危险导致不可预处结果。副作用表达式求值的时候出现的永久性效果 - 命名规定
把宏名全部大写函数名不要全部大写
x+1;//不带副作用
x++;//带副作用
#define 替换规则
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果它们首先替换
2. 替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值替换
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,如果是重复上述操作
注意:
- 宏参数和#define定义中可以出现其他#define定义的变量。对于宏不能递归
- 当预处理器搜素#define定义的符号的时候,字符串常量的内存并不被搜素
用宏实现offsetof 面试题
offsetof求一个结构成员相对于结构开头的字节偏移量
代码实现
#include<stdio.h>
#define OFFSETOF(struct_name,number_name) (int)&(((struct_name*)0)->number_name)
struct s
{
char c1;
int a;
char c2;
};
int main()
{
printf("%d\n", OFFSETOF(struct s, c1));
printf("%d\n", OFFSETOF(struct s, a));
printf("%d\n", OFFSETOF(struct s, c2));
return 0;
}
宏与函数对比
条件编译
常见的条件编译指令
1.
#if 常量表达式
//...
#endif
如
#define __DEBUG__1
#if __DEBUG__
//...
#endif
2.多分支条件编译
#if 常量表达式
//。。。
#elif
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !definef(symbol)
#ifndef symbol
有时我们会对头文件重复包含,那么这时就需要条件编译
在每个头文件开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件内容
#endif
//或者
#pragma once