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
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值