C语言程序开发中,怎样检查接收到的参数是指针还是数组?
发表于: 2019-07-19 11:26:19 | 已被阅读: 26 | 分类于: C语言
我的上一篇文章讨论了 Linux 内核C语言源码中的两个宏:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
这两个宏有实用价值吗
BUILD_BUG_ON_XX 宏只能判断常量表达式,似乎没有什么应用价值,毕竟两个常量的对比谁会弄错呢?有读者(@Gerafore)不知道哪种场合用的是,甚至还有读者(@帖木兒)认为这样的宏根本就没有什么用处。
其实 BUILD_BUG_ON_XX 宏就相当于计算器,1 与 0 对比没人弄错,若是常量表达式在复杂一些呢?例如 1234 * 4321 和 2223 * 2322 的对比,如果再复杂些呢?要程序员自己手动去计算这些,就有些丢失编程的意义了,毕竟编程本来是希望计算机处理这些计算的。
另外,在实际的C语言程序开发中,通常都会用到大量的宏,要是每次用到宏,都去翻一翻它的值,再手动计算对比该有多烦啊!
BUILD_BUG_ON_XX 宏的应用还有很多,例如它还可以和一些C语言编译器内置函数结合使用,比如
实例
我之前有文章曾讨论过如何使用 sizeof() 关键字计算C语言数组长度:
#define arr_len(arr) (size_t)(sizeof(arr)/sizeof(*arr))
但是需要注意的是,若想 arr_len 宏能够正确计算数组长度,只能传递给它数组名,但是C语言中的数组和指针关系暧昧,很难保证程序员不会误传指针给 arr_len() 宏,例如下面这段C语言代码:
void fun(char a[])
{
...
size_t len = arr_len(a);
...
}
char arr[16] = {0};
fun(arr);
虽然 fun() 函数的参数被写成数组形式(char a[]),但是如果读者看过我之前的文章,应该会明白在 fun() 函数内部,a 其实是会退化成指针的。因此 fun() 函数内部的 arr_len 宏计算 arr 长度时,其实计算的是指针的长度,而不是数组的长度,这就极可能引发 bug,并且这个 bug 会隐藏的比较深,难以发现。
出现这样的问题,是因为 arr_len 宏不能检查传递给自己的是否数组。那有没有办法确保传递给 arr_len 宏的一定是数组呢?自然是有的,结合
#define must_be_array(a) \
BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))
如果 a 是指针,那么 a 的数据类型与 &a[0] 的数据类型相同,
must_be_array(a) 宏能够确保 a 一定是数组,否则就会报错,这就为C语言程序提供了安全检查,并且这个检查是在编译时进行的,能够帮助程序员在程序开发阶段发现错误。
现在对 arr_len 宏做修改,相关C语言代码如下,请看:
#define arr_len(arr) \
({ \
must_be_array(arr); \
(size_t)(sizeof(arr)/sizeof(*arr)); \
})
#include <stdio.h>
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
#define must_be_array(a) \
BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))
#define arr_len(arr) \
({ \
must_be_array(arr); \
(int)(sizeof(arr)/sizeof(*arr)); \
})
int main()
{
int arr1[16] = {0};
int *arr2 = NULL;
printf("%d %d\n", arr_len(arr1), arr_len(arr2));
return 0;
}
int main()
{
int arr1[16] = {0};
int *arr2[32] = {0};
printf("%d %d\n", arr_len(arr1), arr_len(arr2));
return 0;
}
# gcc t2.c
# ./a.out
16 32
可见,arr_len现在安全多了,它能够计算数组长度(包括指针数组),也能够判断传递给自己的究竟是指针还是数组。
小结
本节讨论了上一节介绍的两个宏的实用实例,并给出了一个具体的C语言程序示例,可见,即使自定义的
另外,我的这两篇文章并不仅仅是讨论 BUILD_BUG_ON_XX 宏,我更希望是向初学者介绍C语言程序开发中的灵活思想。