定义宏时也可以带上一定的参数,让宏在某种程序上具有“函数”的功能,比如:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define SQR(x) x * x
|
则SQR(6)将会展开成 6 6。但是在在求取SQR(a + b)时,这个宏会展开成 a + b a + b。这显然与想要的结果不符合。为了解决这个问题,可以在宏里使用括号,如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define SQR(x) ((x) * (x))
|
最外层的括号也别省略,看例子:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define SUM(x) (x) + (x)
#define SUM(x) ((x) + (x))
#define SUM(x) ({typeof(x) __x = (x); __x + __x;})
|
如果x的值是个表达式5 3,而代码又写成这样:SUM
SUM
,宏展开之后将变成:(5 3) + (5 3) (5 3) + (5 * 3)。这显然也是不对的,所以最外层的括号也别省略。
对于有参数的宏,最保险的做法就是,在所有参数两边都加上括号,然后在整个表达式加上括号。
另外,如果需要严谨一些的话,在定义有参数宏时,还需要自增自减运算符的影响。请对比以下两个MAX宏,分析自增自减运算符对运算结果的影响。
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
//#define MAX1(a, b) ((a) > (b) ? (a) : (b))
#define MAX2(a, b) \
({ \
typeof(a) _a = a; \
typeof(b) _b = b; \
_a > _b ? _a : _b; \
})
int main()
{
int a = 3;
int b = 2;
int c = MAX1(a++, b); //当使用MAX2宏时结果又如何?
printf("c is %d\n", c);
printf("a is %d\n", a);
return 0;
}
|
预定义好的宏
linux gcc中还定义了几可以直接使用的宏:
_FILE_ 表示正在编译的文件名字符串
_LINE_ 表达正在编译行号
_func_ 表达正在编译的函数名字符串
_DATE_ 表示编译时刻的日期字符串
_TIME_ 表示正在编译时刻的时间字符串
用宏定义语句
可以用宏来定义语句,如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define LOG(format, arg) printf(format, arg)
|
则LOG(“%d”, 5)会展开成printf(“%d”, 5)
当语句跨行时,用\进行连接成一行,如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define DEBUG printf(“[%s][%s][%d]”, __FILE__, __func__, __LINE); \
printf(“:error\n”);
#define SAFEFREE(p) do{\
if(p != NULL)\
{\
free(p);\
p = NULL;\
}\
}while(0)
|
可变参宏
C99标准以后,gcc在预处理阶段,可以用可变参数宏_VA_ARGS_来进行可变参数替换,可变参数的格式是… ,如下:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define LOG(format, …) printf(format, ##__VA_ARGS__)
|
LOG(“%s\n”, “abc”) ==> printf(“%s\n”, “abc”);
LOG(“%s %d\n”, “abc”, 10) ==> printf(“%s %d\n”, “abc”, 10);
“#”运算符
在宏定义中,可以使用#将参数转化成字符串的形式,比如:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define PRINT(x) printf(#x)
#define DEBUG(x) printf(“the num “ #x “is :%d\n”, x);
|
则:
PRINT(abcdef) ==> printf(“abcdef”);
PRINT(12345) ==> printf(“12345”)
DEBUG(5) ==> printf(“the num 5 is :%d\n”, 5);
“##”运算符
##运算符可在宏定义中进行字符串的连接,比如:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define NMAE(n) a##n
|
则:
NAME(5) ==> a5
条件编译
条件编译的功能使得我们可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于调试和移植程序是很有用的,条件编译有以下几种形式。
第1种形式
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#ifdef 标识符
程序段1
#endif
|
第2 种形式
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#ifdef 标识符
程序段1
#else
程序段2
#endif
|
第3种形式
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#ifdef 标识符
程序段1
#elif 标识符2
程序段2
#elif 标识符3
程序段3
......
#endif
|
第4种形式
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#ifndef 标识符
程序段1
#endif
|
第5种形式
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#if 常量表达式
程序段1
#else
程序段2
#endif
|
第6种形式
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#if defined(宏1)
程序段1
#elif defined(宏2) && defined(宏3)
程序段2
#elif !defined(宏4) && !defined(宏5)
程序段3
#endif
|
文件包含
文件包含是预处理的一个重要功能,它将多个源文件链接成一个源文件进行编译,结果将只生成一个目标文件。C语言提供#include命令来实现文件的包含,它有两种格式。
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#include
#include “filename”
|
其中,filename是要包含的文件名称,也称为头文件。用尖括号括起时,表示这个文件要到系统规定的路径中去获得这个文件(即C编译系统提供的存放头文件的路径,一般是/usr/include目录)。而用双引号括起的头文件,则表示这个头文件要到当前目录中去寻找,如果没找到,则到系统指定的目录去寻找。
头文件
在程序有多个源文件时,可以将函数或类型的声明写到头文件中,头文件的后缀一般是.h结尾。对于头文件的使用,最重要的是防止头文件重复包含。一般使用如下的格式避免这个问题。
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#ifndef 头文件标识符
#define 头文件标识符
//头文件内容
#endif
|
注意一点,即使使用了防止重复包含语句,也不要在头文件中分配内存(定义变量或是malloc堆内存),因为使用了防止重复包含语句,也只是让一个文件中的多条#include语句防止重复包含,有多个源文件都包含同一个头文件时,定义的变量仍然会被定义很多次,产生重复定义的错误。所以,一般,全局变量都要放到源文件中去定义。
gcc编译流程
一般C语言的编译流程分为四步,分别是:预处理,编译,汇编,链接。
预处理主要操作是的程序中以#开头的预处理指令,包括宏定义,条件编译,文件包含三项内容,预处理的选项是-E,生成的文件以.i作为后缀。
代码块 |
---|
language | bash |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
gcc -E hello.c -o hello.i
|
预处理之后,编译器就可以开始把源文件翻译成机器码了。但是在编译之前,对于gcc编译器来说,还存在一个中间步骤,会把源文件先转化为汇编语言,然后再转化成机器码,转化为汇编语言这一步称为编译,使用-S选项。
代码块 |
---|
language | bash |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
gcc -S hello.i -o hello.s
|
转化成汇编代码后,再把汇编代码转化成机器码,这一步称为汇编,gcc的汇编选项是-c,生成的文件以.o作为后缀,称为目标文件。
代码块 |
---|
language | bash |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
gcc -c hello.s -o hello.o
|
最后一步,是将各个目标文件以及系统提供的库文件一起进行链接,生成一个最终的可执行文件。
代码块 |
---|
language | bash |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
gcc hello.o -o hello
|
用宏可以实现的一些骚操作,比如:
代码块 |
---|
language | c++ |
---|
theme | Confluence |
---|
linenumbers | true |
---|
collapse | false |
---|
|
#define HI_APPCOMM_LOG_AND_RETURN_IF_FAIL(ret, errcode, errstring) \
do { \
if ((ret) != HI_SUCCESS) { \
MLOGE("[%s] failed[0x%08X]\n", (errstring), (ret)); \
return (errcode); \
} \
} while (0)
|
上面这个宏可以处理常见的根据返回值退出的操作。