C/C++预定义宏、 #line 、#error、 #pragma和泛型选择

预定义宏

C标准规定了一些预定义宏:
预 定 义 宏

_ _func_ _是C语言的预定义标识符

C99 标准提供一个名为_ _func_ _的预定义标识符,它展开为一个代表
函数名的字符串(该函数包含该标识符)。那么,_ _func_ _必须具有函数
作用域,而从本质上看宏具有文件作用域。因此,_ _func_ _是C语言的预定
义标识符,而不是预定义宏。

下面程序中使用了一些预定义宏和预定义标识符。注意,其中一
些是C99 新增的,所以不支持C99的编译器可能无法识别它们。如果使用
GCC,必须设置-std=c99或-std=c11。

predef.c程序:

// predef.c -- 预定义宏和预定义标识符
#include <stdio.h>
void why_me();
int main(){
	printf("The file is %s.\n", __FILE__);
	printf("The date is %s.\n", __DATE__);
	printf("The time is %s.\n", __TIME__);
	printf("The version is %ld.\n", __STDC_VERSION__);
	printf("This is line %d.\n", __LINE__);
	printf("This function is %s\n", __func__);
	why_me();
	return 0;
}
void why_me(){
	printf("This function is %s\n", __func__);
	printf("This is line %d.\n", __LINE__);
}

下面是该程序的输出:

The file is predef.c.
The date is Sep 23 2013.
The time is 22:01:09.
The version is 201112.
This is line 11.
This function is main
This function is why_me
This is line 21.

#line和#error

#line指令重置_ _LINE_ __ _FILE_ _宏报告的行号和文件名。可以这
样使用#line:

#line 1000 // 把当前行号重置为1000
#line 10 "cool.c" // 把行号重置为10,把文件名重置为cool.c

#error 指令让预处理器发出一条错误消息,该消息包含指令中的文本。
如果可能的话,编译过程应该中断。可以这样使用#error指令:

#if _ _STDC_VERSION_ _ != 201112L
#error Not C11
#endif

编译以上代码生成后,输出如下:

$ gcc newish.c
newish.c:14:2: error: #error Not C11
$ gcc -std=c11 newish.c
$

如果编译器只支持旧标准,则会编译失败,如果支持C11标准,就能成
功编译。

#pragma

在现在的编译器中,可以通过命令行参数或IDE菜单修改编译器的一些
设置。#pragma把编译器指令放入源代码中。例如,在开发C99时,标准被
称为C9X,可以使用下面的编译指示(pragma)让编译器支持C9X:

#pragma c9x on

一般而言,编译器都有自己的编译指示集。例如,编译指示可能用于控
制分配给自动变量的内存量,或者设置错误检查的严格程度,或者启用非标
准语言特性等。C99 标准提供了 3 个标准编译指示。

C99还提供_Pragma预处理器运算符,该运算符把字符串转换成普通的
编译指示。例如:

_Pragma("nonstandardtreatmenttypeB on")

等价于下面的指令:

#pragma nonstandardtreatmenttypeB on

由于该运算符不使用#符号,所以可以把它作为宏展开的一部分:

#define PRAGMA(X) _Pragma(#X)
#define LIMRG(X) PRAGMA(STDC CX_LIMITED_RANGE X)

然后,可以使用类似下面的代码:

LIMRG ( ON )

顺带一提,下面的定义看上去没问题,但实际上无法正常运行:

#define LIMRG(X) _Pragma(STDC CX_LIMITED_RANGE #X)

问题在于这行代码依赖字符串的串联功能,而预处理过程完成之后才会
串联字符串。

_Pragma 运算符完成“解字符串”(destringizing)的工作,即把字符串中
的转义序列转换成它所代表的字符。因此,

_Pragma("use_bool \"true \"false")

变成了:

#pragma use_bool "true "false

泛型选择(C11)

在程序设计中,泛型编程(generic programming)指那些没有特定类
型,但是一旦指定一种类型,就可以转换成指定类型的代码。例如,C++在
模板中可以创建泛型算法,然后编译器根据指定的类型自动使用实例化代
码。

C没有这种功能。然而,C11新增了一种表达式,叫作泛型选择表达式
(generic selection expression),可根据表达式的类型(即表达式的类型是
int、double 还是其他类型)选择一个值。泛型选择表达式不是预处理器指
令,但是在一些泛型编程中它常用作#define宏定义的一部分。
下面是一个泛型选择表达式的示例:

_Generic(x, int: 0, float: 1, double: 2, default: 3)

_Generic是C11的关键字。_Generic后面的圆括号中包含多个用逗号分隔
的项。第1个项是一个表达式,后面的每个项都由一个类型、一个冒号和一
个值组成,如float: 1。第1个项的类型匹配哪个标签,整个表达式的值是该
标签后面的值。例如,假设上面表达式中x是int类型的变量,x的类型匹配
int:标签,那么整个表达式的值就是0。如果没有与类型匹配的标签,表达式
的值就是default:标签后面的值。泛型选择语句与 switch 语句类似,只是前
者用表达式的类型匹配标签,而后者用表达式的值匹配标签。
下面是一个把泛型选择语句和宏定义组合的例子:

#define MYTYPE(X) _Generic((X),\
int: "int",\
float : "float",\
double: "double",\
default: "other"\
)

宏必须定义为一条逻辑行,但是可以用 \ 把一条逻辑行分隔成多条物理
行。

在这种情况下,对泛型选择表达式求值得字符串。例如,对
MYTYPE(5)求值得"int",因为值5的类型与int:标签匹配。

下面程序演示了这种用法:

mytype.c程序

// mytype.c
#include <stdio.h>
#define MYTYPE(X) _Generic((X),\
int: "int",\
float : "float",\
double: "double",\
default: "other"\
)
int main(void)
{
int d = 5;
printf("%s\n", MYTYPE(d)); // d 是int类型
printf("%s\n", MYTYPE(2.0*d)); // 2.0 * d 是double类型
printf("%s\n", MYTYPE(3L)); // 3L是long类型
printf("%s\n", MYTYPE(&d)); // &d 的类型是 int *
return 0;
}

下面是该程序的输出:

int
double
other
other

MYTYPE()最后两个示例所用的类型与标签不匹配,所以打印默认的字
符串。可以使用更多类型标签来扩展宏的能力,但是该程序主要是为了演示
_Generic的基本工作原理。

对一个泛型选择表达式求值时,程序不会先对第一个项求值,它只确定
类型。只有匹配标签的类型后才会对表达式求值。

可以像使用独立类型(“泛型”)函数那样使用_Generic 定义宏。

参考

《C Primer Plus》

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林树杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值