如果你是 C++ 程序员,应该接触过 C++11 里的 decltype 操作符,它的作用是自动推导表达式的数据类型,以解决泛型编程中有些类型由模板参数决定而难以(甚至不可能)表示的问题。其实这个特性在 C 语言中也早有类似的实现,GNU C 标准中的一个扩展特性 typeof (PS: 不是 typedef)作用与 decltype 类似,我们来看看这个关键字该怎么用。
先来看一个最简单的例子:
// demo 01
int var = 666;
typeof(int *) pvar = &var;
printf("pvar:\t%p\n", pvar);
printf("&var:\t%p\n", &var);
printf("var:\t%d\n", var);
printf("*pvar:\t%d\n", *pvar);
我们先定义了一个 int 型变量 var,然后再定义一个指针型变量指向 var,一般我们就直接 int *xxx = &xx,但是我们为了演示 typeof 的用法,就不要这么直接了,typeof 是自动推导后面 ( ) 里的数据类型,所以 typeof(int *) 直接推导出了 int * 型,用这个类型声明了 pvar 并将其初始化为 var 的地址,输出结果应该就显而易见了,这是在我的机器上的输出:
好吧我承认上面那个例子是吃力不讨好,明明写个 int * 简单又明了,非得加个 typeof 搞得这么晦涩,其实 typeof 的功效在于其能够自动推导表达式类型,比如我们把刚才的 typeof(int *) 改成 typeof(&var),它也会自动推导出 &var 的类型 —— int * 型,你可以自己试一下,原理是一样的,这样的话,当遇到一个非常复杂的表达式我们很难推断其类型的时候,typeof 就很有用了。另外有一点要注意:typeof 是 GNU C 标准里特有的扩展,标准的 ISO C 并没有这个关键字,所以在编译的时候不能加任何 ISO 的 C 标准选项,否则会报错,比如编译上面的代码我加入了一个 -std=c90 的选项,编译器就会有提示一堆 error:
解决的方法很简单,把 -std=c90 改成 -std=gnu90 即 GNU 的标准即可。
再来几个例子,比如
// demo 02
int *pvar = NULL;
typeof(*pvar) var = 999;
printf("var:\t%d\n", var);
再来:
// demo 03
int *pvar = NULL;
typeof(*pvar) var[4] = {11, 22, 33, 44};
for (int i = 0; i < 4; i++)
printf("var[%d]:\t%d\n", i, var[i]);
这次 typeof 解析出来的类型跟上一个一样,区别是 var 是一个包含四个元素的数组,相当于 int var[4] = {...}; 输出如下:
这次来个有点水平的:
// demo 04
typeof(typeof(const char *)[4]) pchar = {"hello", "world", "good", "night"};
for (int i = 0; i < 4; i++)
printf("pchar[%d]:\t%s\n", i, pchar[i]);
再来考验一下你的指针,这次是函数指针:
// demo 05
int add(int param1, int param2) {
return param1 + param2;
}
int sub(int param1, int param2) {
return param1 - param2;
}
int mul(int param1, int param2) {
return param1 * param2;
}
int main() {
int (*func[3]) (int, int) = {add, sub, mul};
typeof(func[0](1, 1)) sum = 100;
typeof(func[1](1, 1)) dif = 101;
typeof(func[2](1, 1)) pro = 102;
printf("sum:\t%d\n", sum);
printf("dif:\t%d\n", dif);
printf("pro:\t%d\n", pro);
return 0;
}
我们再看看它在宏定义中的应用:
// demo 06
#define pointer(T) typeof(T *)
#define array(T, N) typeof(T[N])
int main() {
array(pointer(char), 4) pchar = {"hello", "world", "good", "night"};
for (int i = 0; i < 4; i++)
printf("pchar[%d]:\t%s\n", i, pchar[i]);
return 0;
}
好了,啰嗦了这么多,typeof 这个关键字总算是知道用来干什么了吧,感觉好像语法挺晦涩的,而且没有什么实际用途,那好吧,我再让大伙看一看实际项目中的一个例子:
/*
* 选自 linux-2.6.7 内核源码
* filename: linux-2.6.7/include/linux/kernel.h
*/
#define min(x,y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
上面这个例子是选自 linux 2.6.7 内核中 include/linux/kernel.h 这个头文件,宏定义 min 的作用是从两个相同类型的对象中选取一个最小的,它接受两个参数 x 和 y,后面的宏替换部分就用 typeof 定义两个变量 _x 和 _y,并分别赋值为 x y,这里用 typeof 的作用就是可以让 min 接受任何类型的参数而不必局限于某一个单一类型,这有点泛型编程的味道了,最后一个语句 _x < _y ? _x : _y; 用了一个条件运算符来返回二者之中最小的,中间还有一句 (void) (&_x == &_y); 看起来好像是废话,其实这句话是有特殊用意的,因为我们不能保证你在使用 min 的时候传入的两个参数都是相同的类型,这时候就需要做一个检测,而 C 语言不支持直接 typeof(_x) == typeof(_y) 这样的操作,所以就取其地址,用指针类型来比较,如果两个指针的类型不一致,编译器就会产生警告以达到检测的效果,至于前面的 (void),是因为仅表达式 &_x == &_y 本身是没有意义的,如果没有这个 (void) 编译器同样会警告:statement with no effect [-Wunused-value],无效的语句,如果不想看到这个警告,那就在前面加个
(void) 忽略掉。
参考资料:
GNU 官方手册:https://gcc.gnu.org/onlinedocs/gcc/Typeof.html