_ _func_ _
是C语言的预定义标识符C99 标准提供一个名为_ _func_ _
的预定义标识符,它展开为一个代表
函数名的字符串(该函数包含该标识符)。那么,_ _func_
_必须具有函数
作用域,而从本质上看宏具有文件作用域。因此,_ _func_ _
是C语言的预定
义标识符,而不是预定义宏。
下面程序中使用了一些预定义宏和预定义标识符。注意,其中一
些是C99 新增的,所以不支持C99的编译器可能无法识别它们。如果使用
GCC,必须设置-std=c99或-std=c11。
predef.c程序:
// predef.c -- 预定义宏和预定义标识符
#include
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指令重置_ _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标准,就能成
功编译。
在现在的编译器中,可以通过命令行参数或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
在程序设计中,泛型编程(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
#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》