我试图编写一个宏来检测 struct 成员是灵活数组还是常规array.

事实证明,clang将灵活的数组类型视为不完整(尚未调整大小)的数组类型.

在不同的兼容性测试中,可以通过与不同的特定大小兼容来检测不完整的数组类型:

#define ISCOMPWITHARRAYOFN(LVAL,N) _Generic((typeof((LVAL)[0])(*)[N])0, default:0,typeof(&(LVAL)):1)
#define ISINCOMPLETE_ARRAY(LVAL) ( ISCOMPWITHARRAYOFN(LVAL,1) && ISCOMPWITHARRAYOFN(LVAL,2) )

extern char incomplete[];
extern char complete[1];

//accepted by both gcc and clang
_Static_assert(ISINCOMPLETE_ARRAY(incomplete),"");
_Static_assert(!ISINCOMPLETE_ARRAY(complete),"");

这意味着在叮当作响时,我可以拥有:

#define ISFLEXIBLE(type,member) ISINCOMPLETE_ARRAY((type){0}.member)

struct flexed{ int a; char m[]; };
struct unflexed0{ int a; char m[1]; };
struct unflexed1{ int a; char m[1]; int b; };

//both GCC and clang accept these:
_Static_assert(!ISFLEXIBLE(struct unflexed0,m),"");
_Static_assert(!ISFLEXIBLE(struct unflexed1,m),"");

//only clang accepts these
_Static_assert(ISFLEXIBLE(struct flexed,m),"");

_Static_assert(ISCOMPWITHARRAYOFN((struct flexed){0}.m,1),"");
_Static_assert(ISCOMPWITHARRAYOFN((struct flexed){0}.m,2),"");

但GCC并不接受这一点.

我的问题是,GCC中的哪一个在这里表现不正确,能不能写下 一个(可能是非标准的)ISFLEXIBLE(type,array_typed_member)宏能同时在两个编译器上运行?

https://godbolt.org/z/e8jb19bbK

推荐答案

这个问题可以用一行代码重现:

_Static_assert(_Generic(&(struct {int a, m[]; }){0}.m, default: 0, int (*)[1]: 1), "");

Cang接受了这一点.GCC则不这么认为.如果把前1改成0,GCC就接受了(郎朗也接受了).这表明Clang将柔性数组成员视为长度未知的数组,而GCC将柔性数组成员视为零长度的array.

C 2018 6.7.2.1 18表示:

作为特例,具有多个命名成员的 struct 的最后一个成员可能具有不完整的数组类型;这称为flexible array member…然而,当.(或->)运算符的左操作数是具有灵活数组成员的 struct (指向该 struct 的指针)并且右操作数命名该成员时,它的行为就好像该成员被替换为最长的数组(具有相同的元素类型),而该数组不会使该 struct 大于被访问的对象;…如果这个数组没有元素,它的行为就像它有一个元素一样,但如果试图访问该元素或在它后面生成一个指针,则行为是未定义的.

因此, struct 的最后一个成员具有不完整的数组类型,就像Clang对待它一样.但接下来我们必须考虑有关使用.->的表达式的陈述.也许这些语句仅用作有关程序行为的语句,而不是表达式的类型,即有关运行时操作(如读写数组元素)的语句.如果是这样,Clang将其视为不完整的数组类型是正确的.

然而,如果语句的目的是指定类型,那么GCC仍然是错误的,因为如果大小不允许任何元素,那么应该将其视为一个元素的array.但GCC将其视为由零元素组成的array.

进一步的实验表明,即使 struct 是使用具有灵活数组成员(这是GCC的扩展)的元素的初始值设定项创建的,GCC仍然将其类型视为由零元素组成的数组,即使它实际上有更多元素.

所以GCC不符合C标准.

Clang的解释是合理的;当用.->引用数组时,将其视为完整数组,在某些情况下(例如,当向其传递指向这些 struct 之一的指针时,该指针可能指向具有足够空间用于数组元素的内存),将需要在编译时不可用的知识.

C++相关问答推荐

理解C中的指针定义

在C中使用强制转换将uint16_t转换为uint8_t [2]是否有效?

Ebpf内核代码:permission denied:invalid access to map value

当打印字符串时,为什么在c中没有使用常量限定符时我会收到警告?

为什么内核使用扩展到前后相同的宏定义?

LibpCap禁用监视器模式(C、MacOS)

Clang:如何强制运行时错误的崩溃/异常由于-fsanitize=undefined

在C23中使用_GENERIC实现带有右值的IS_POINTER(P)?

使用AVX2的英特尔2022编译器的NaN问题&;/fp:FAST

平均程序编译,但结果不好

SSH会话出现意外状态

为什么数组的最后一个元素丢失了?

C将数组传递给函数以修改数组

如何在c中使用具有不同变量类型的内存分配?

";错误:寄存器的使用无效;当使用-masm=intel;在gcc中,但在AT&;T模式

Ubuntu编译:C中的文件格式无法识别错误

为什么写入关闭管道会返回成功

为什么创建局部变量的指针需要过程在堆栈上分配空间?

为什么实现文件中的自由函数默认没有内部链接?

C 中 struct 体自赋值是否安全?特别是如果一侧是指向 struct 的指针?