给定以下宏,用于_Generic确定参数是否是数组(而不是指针):
#include <stdio.h>
#define IS_ARRAY(T) \
_Generic( &(T)[0], \
typeof(T) : 0, \
default : 1 \
)
int a[2];
int *p = a;
int main() {
printf( "IS_ARRAY(a) = %d\n", IS_ARRAY(a) );
printf( "IS_ARRAY(p) = %d\n", IS_ARRAY(p) );
}
Run Code Online (Sandbox Code Playgroud)
和:
$ clang --version
clang version 17.0.6
$ clang -std=c2x a.c
Run Code Online (Sandbox Code Playgroud)
我得到:
a.c:15:33: warning: due to lvalue conversion of the controlling expression, association of type 'typeof ((a))' (aka 'int[2]') will never be selected because it is of array type [-Wunreachable-code-generic-assoc]
15 | printf( "IS_ARRAY(a) = %d\n", IS_ARRAY(a) );
| ^
a.c:7:5: note: expanded from macro 'IS_ARRAY'
7 | typeof( (T) ) : 0, \
| ^
1 warning generated.
Run Code Online (Sandbox Code Playgroud)
然而:
$ ./a.out
IS_ARRAY(a) = 1
IS_ARRAY(p) = 0
Run Code Online (Sandbox Code Playgroud)
尽管有警告,但似乎仍然有效。这里的警告是不是错了?
这意味着 的第一个表达式_Generic按照“左值转换”(C17 中的新更改)进行转换,并且根据转换后的值检查下面的通用关联列表。
这是因为在C11中,如何处理内部的限定符是不明确的_Generic。一些编译器允许类型+限定符const int: 1, int: 2等作为两种单独的情况,但有些编译器则不允许。因此,作为修复,C17 保证传递表达式的左值转换,因此它永远不会出现const int在我的示例中。请注意,当我们尝试执行此操作时,clang 如何给出与您得到的警告相同的警告:
#define IS_INT(x) _Generic((x), int: 1, const int: 1)
Run Code Online (Sandbox Code Playgroud)
就此而言,它也不能成为数组类型,因为数组无法分配/左值转换。您收到的警告是针对typeof(T)数组类型的。
(&T)[0]不是一个正确的修复,因为这会将数组变成指针,然后再次变成数组,然后它会衰减......然后你会得到一个int*.
&(T)[0]不是正确的修复方法,因为这将取消引用数组,类型int,然后您将获得该数组的地址,int*。
注意优先[]于一元&。
但是,您可以做的是获取传递的表达式的地址并检查它是否是指向数组的指针。
作为另一个小技巧,类型int (*)[](指向不完整类型数组的指针)int (*)[n]无论如何都与每个数组指针兼容n(请参阅“复合类型”C17 6.2.7)。
固定代码,符合 C23:
#define IS_ARRAY(T) \
_Generic( &(T), \
typeof(*T) (*)[] : true, \
default : false \
)
Run Code Online (Sandbox Code Playgroud)
int a[2];给它,那么数组不会“衰减”为指向第一个元素的指针,而是将&(T)其转换为int (*)[2]。typeof(*T)然而,它会像往常一样衰减数组,我们最终得到int.int (*)[2]兼容,int (*)[]因此它是一个数组并且具有正确的元素类型。