程序的编译分为四个阶段:
预处理:gcc -o test.i -E test.c 展开头文件/宏替换/不进行语法检查。
编译:gcc -o test.s -S test.i 进行语法检查
汇编:gcc -o test.o -c test.s 生成目标文件
连接: gcc -o test test.o 生成可执行文件
预处理详解:
预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
例:
#include<stdio.h>
int main()
{
printf("file:%s line:%d\n", __FILE__, __LINE__);
return 0;
}
#define 定义标识符
//语法
#define name stuff
如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t\
date:%s\ttime:%s\n",\
__FILE__,__LINE__,\
__DATE__,__TIME__)
在define定义标识符的时候,建议不要加上‘;’,这样容易导致问题。例如:
#define MAX 1000;
if(condition)
max=MAX;
else
max=0;
condition为真时,max=1000;; 有两个分号,else无法与if对齐,出现语法错误
用于对表达式进行求值的宏定义都应该在表达式两边加上一堆括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
例:
#define ADD(num) num+num
printf("%d\n", ADD(2) * ADD(3));
最后输出为11。原因是2+2*3+3=11
应改为:
#define ADD(num) ((num)+(num))
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1、在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2、替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值替换。
3、最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1、宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
2、当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
---------------------------------------------------------------------------------------------------------------------------------
使用#,可以把一个宏参数变成对应的字符串
#include<stdio.h>
#define PRINT(FORMAT,VALUE)\
printf("the value of "#VALUE" is "FORMAT"\n",VALUE);
int main()
{
int i = 2;
PRINT("%d", i + 3);
return 0;
}
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
#include<stdio.h>
#define ADD_TO_SUM(num,value) sum##num+=value;
int main()
{
int sum3 = 0;
ADD_TO_SUM(3, 15); //作用是给sum3加上15
printf("sum3 = %d\n", sum3);
return 0;
}
# 取宏参数的值
## 连接
---------------------------------------------------------------------------------------------------------------------------------
宏和函数的对比
属 性 | #define定义宏 | 函数 |
代码 长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使它们执行的任务是不同的。 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
命名约定
一般来讲函数与宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是:
把宏名全部大写,函数名不要全部大写。