C 预处理指令及预定义宏
#
#
运算符,将宏参数转化为字符串。
#define LOG(x) printf("%s\n", #x)
int aa = 1;
LOG(aa); // 展开为 printf("%s\n", "aa"),即参数原样转成字符串
##
##
运算符,拼接字符串。
#define INVILED (-1)
#define INVILED_STR "参数错误"
#define LOG(ERROR) printf("[code:%d]:%s\n", ERROR, ERROR##_STR)
LOG(INVILED); // 输出为:[code:-1]:参数错误
#define
- 定义一个宏,其后第一个字段是宏的名字,第二个字段及以后是宏的值,在预处理过程中,宏的名字被替换换成值。
- 宏的值后面允许使用
//
/**/
进行注释。 - 宏定义的作用域是从定义处到文件尾,除非使用
#undef
取消宏定义。 - 宏允许定义在C文件中的任何位置,包括函数体内部。
- 宏的值中不能嵌套
#define
#if
#error
#warning
等预处理指令。
#define _PI 3.1415926 // 宏的值是一个常量
#define PI _PI // 宏的值是一个宏,即宏允许嵌套
#define AA(X,Y) (X+Y) // 宏可以使用参数列表,但是宏不会检查参数列表的类型
#error
用于在编译期间打印错误信息,编译会终止。通常配合预处理条件指令。
#define LEN 5
#if LEN > 5
#error LEN must less then 5. // error后面若有"号,也会被打印出
#endif
#if/else/elif/else if/if/endif
该条件宏支持常量表达式判断,但是不支持枚举类型的变量。
#define AA 5
#define BB 6
#define CC 1
#if BB - AA == CC
...
#else
...
#endif
#include
包含文件,被包含的可以是任意后缀的问价,但通常是头文件,即.h
文件。
#include <stdio.h> // 通常库文件使用 <> 符号,编译器先去系统目录中找头文件,如果没有在到当前目录下找
#include "aa.h" // 自定义的头文件使用 "",编译器先在当前目录下寻找,如果找不到,再到系统目录中寻找
#include_next
若GCC的搜索路径中存在两个重名的文件,#include
指向搜索到的第一个文件,#include_next
指向搜索到的第二个文件。通常用于同名的头文件之间的包含。
/home/xflm/test/stdio.h
#warning include user stdio.h #include_next <stdio.h> // #include_next "stdio.h" // 与上一行等价,也即不区分 <> 和 ""
/home/xflm/test/main.c
#include "stdio.h" // 使用用户定义的stdio.h,打印warning: include user stdio.h // #include <stdio.h> // 使用标准库,不打印warning
在源文件使用时,GCC会报警告:#include_next in primary source file,即GCC将 #include_next 修改为了 #include
#line
用于改变__LINE__
__FILE__
的值。
#line 5 // 下一行的行号为5,然后递增
#line 5 "11/aa.c" // 下一行的行号为5,然后递增,__FILE__ 的值修改为:11/aa.c
#pragma
设定编译器的状态或者是指示编译器完成一些特定的动作。参考#pragma命令详解
#pragma message("hello") // 打印消息
#undef
取消宏定义,C中不允许重复的宏定义,但是可以取消宏定义之后再重新定义。
#define A 5
#undef A // 取消宏定义,若不取消而直接重新定义,编译会报错
#define A 6 // 重新定义
#warning
用于在编译期间打印警告信息,编译不会终止。通常配合预处理条件指令。
#define LEN 5
#if LEN > 5
#warning LEN must less then 5. // warning后面若有"号,也会被打印出
#endif
BASE_FILE
编译时文件名,字符串类型,可以使用字符串拼接的方法拼接,在源文件中同__FILE__
,在头文件中可以表示头文件的名称。
/home/xflm/test/aa/aa.h
const char *basefile = __BASE_FILE__; const char *file = __FILE__;
/home/xflm/test/main.c
#include "aa.h" puts(basefile); // 打印 aa.h,路径的名称和GCC查找的路径有关 puts(file); // 打印 main.c,路径的名称和GCC输入的路径有关
$ gcc ../test/main.c -Iaa $ ./a.out aa/aa.h # GCC找到的路径是 aa/aa.h ../test/main.c # GCC输入的源文件路径是 ../test/main.c
BYTE_ORDER
该宏用于判断CPU的大小端,当编译小端程序时,其值为__ORDER_LITTLE_ENDIAN__
,编译大端程序时,其值为__ORDER_BIG_ENDIAN__
。
main.c
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #warning It's little endian #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #warning It's big endian #endif int main(int argc, char *argv[]) { return 0};
$ arm-none-eabi-gcc -mbig-endian main.c #warning It's big endian $ arm-none-eabi-gcc -mlittle-endian main.c #warning It's little endian
COUNTER
自身计数器,用于记录GCC编译当前源文件过程中出现的__COUNTER__的次数,从0开始计数。常用于构造一系列的变量名称,函数名称等。
/home/xflm/test/aa.h
#define __VAR(x, y) x##y #define _VAR(x, y) __VAR(x, y) #define VAR(x) _VAR(x, __COUNTER__)
/home/xflm/test/main.c
#include "aa.h" int VAR(aaa); int VAR(aaa); int main(int argc, char *argv[]) { return 0};
/home/xflm/test/aa.c
#include "aa.h" int VAR(bbb); int VAR(bbb);
$ gcc main.c aa.c # 编译 main.c 和 aa.c $ nm a.out | grep "aaa\|bbb" 00000000006008a8 B aaa0 00000000006008ac B aaa1 00000000006008b0 B bbb0 // 编译aa.c文件时重新计数 00000000006008b4 B bbb1
__cplusplus
用于判断是否是C++编译器,C编译器中未定义该宏。
#ifdef __cplusplus
#warning C++ compiler // 使用gcc该警告不会打印,使用g++会打印该警告
#endif
DATE
当前编译日期,字符串类型,可以使用字符串拼接的方法拼接,比如:May 15 2021
。
puts(__DATE__ ":hello"); // 打印编译时间,输出:May 15 2021:hello
FILE
编译时文件名,字符串类型,可以使用字符串拼接的方法拼接。
/home/xflm/test/main.c
int main(int argc, char *argv[]) { (void)argc; (void)argv; puts(__FILE__ ":hello"); // 打印编译时的函数名,与编译有关 return 0; }
编译
$ cd /home/xflm/test $ gcc main.c $ ./a.out main.c:hello # 可以字符串拼接 $ gcc ../test/main.c $ ./a.out ../test/main.c:hello # 打印的名字和编译时传给gcc的文件名有关 $ gcc ~/test/main.c $ ./a.out /home/xflm/test/main.c:hello # ~ 被转化为 /home/xflm
func
同__FUNCTION__
,当前函数名,const char *类型,即不能使用字符串拼接的方法拼接。
FUNCTION
当前函数名,const char *类型,即不能使用字符串拼接的方法拼接。
int aa(void)
{
puts(__FUNCTION__); // 打印当前函数名,输出为:aa
}
INCLUDE_LEVEL
宏所在的文件被包含的层数,int类型,始于0。常作为递归包含的限制条件。
/home/xflm/test/aa.h
#if __INCLUDE_LEVEL__ == 0 #warning aa.h: __INCLUDE_LEVEL__ == 0 #elif __INCLUDE_LEVEL__ == 1 #warning aa.h: __INCLUDE_LEVEL__ == 1 #endif
/home/xflm/test/main.c
#include "aa.h" // 会打印:aa.h: __INCLUDE_LEVEL__ == 1 #if __INCLUDE_LEVEL__ == 0 #warning main.c: __INCLUDE_LEVEL__ == 0 // 会打印:main.c: __INCLUDE_LEVEL__ == 0 #elif __INCLUDE_LEVEL__ == 1 #warning main.c: __INCLUDE_LEVEL__ == 1 #endif
LINE
当前行号,int类型,始于1。
100 int aa(void)
101 {
102 printf("line: %d\n", __LINE__); // 打印当前行在源文件中的行号,输出为:102
103 }
SIZEOF_POINTER
目标代码中指针的字节数,整型,通常32位系统为4,64位系统为8。
main.c
#if __SIZEOF_POINTER__ == 4 #warning("__SIZEOF_POINTER__ = 4"); #elif __SIZEOF_POINTER__ == 8 #warning("__SIZEOF_POINTER__ = 8"); #else #warning("__SIZEOF_POINTER__ not define"); #endif
$ gcc -E main.c > /dev/null # 在64位主机上打印8 $ gcc -E main.c -m32 > /dev/null # 在64位主机上打印4
TIME
编译时间,字符串类型,可以使用字符串拼接的方法拼接,比如:22:35:14
,即24格式时分秒。
puts(__TIME__ ":hello"); // 打印编译时间,输出:22:35:14:hello
TIMESTAMP
文件最后一次被修改的时间,字符串类型,可以使用字符串拼接的方法拼接,比如:Sun May 16 14:01:56 2021
。
puts(__TIMESTAMPE__ ":hello"); // 打印编译时间,输出:Sun May 16 14:01:56 2021:hello