检测结构体成员是否灵活以及 gcc 和 clang 哪个错误?

PSk*_*cik 3 c gcc clang language-lawyer

我试图编写一个宏来检测结构成员是灵活数组还是普通数组。

事实证明,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),"");
Run Code Online (Sandbox Code Playgroud)

这意味着在 clang 上,我可以:

#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),"");
Run Code Online (Sandbox Code Playgroud)

但GCC不接受这一点。

我的问题是 gcc/clang 中的哪一个在这里表现不正确,是否可以编写一个ISFLEXIBLE(type,array_typed_member)适用于两个编译器的(可能是非标准的)宏?

https://godbolt.org/z/e8jb19bbK

Eri*_*hil 6

该问题可以用单行重现:

\n
_Static_assert(_Generic(&(struct {int a, m[]; }){0}.m, default: 0, int (*)[1]: 1), "");\n
Run Code Online (Sandbox Code Playgroud)\n

铿锵接受了这一点。海湾合作委员会没有。如果第一个1更改为0,GCC 会接受它(Clang 也是如此)。这表明 Clang 将灵活数组成员视为未知长度的数组,而 GCC 将灵活数组成员视为零长度的数组。

\n

C 2018 6.7.2.1 18 说:

\n
\n

作为一种特殊情况,具有多个命名成员的结构的最后一个成员可能具有不完整的数组类型;这称为灵活数组成员\xe2\x80\xa6 然而,当 a .(或->) 运算符的左操作数是(指向)具有灵活数组成员的结构且右操作数命名该成员时,其行为如下如果该成员被替换为最长的数组(具有相同的元素类型),则不会使结构大于正在访问的对象;\xe2\x80\xa6 如果此数组没有元素,则其行为就像有一个元素一样元素,但如果尝试访问该元素或生成超过该元素的指针,则行为未定义。

\n
\n

因此,该结构的最后一个成员具有不完整的数组类型,正如 Clang 所处理的那样。.但接下来我们必须考虑使用or的有关表达式的语句->。也许这些只是关于程序行为的语句,而不是表达式的类型,即关于运行时操作(例如读取和写入数组元素)的语句。如果是这样,Clang 将其视为不完整的数组类型是正确的。

\n

然而,如果这些语句旨在指定类型,那么 GCC 仍然是错误的,因为如果大小不允许任何元素(正如复合文字可能所做的那样),那么它应该被视为一个元素的数组。但 GCC 将其视为零元素数组。

\n

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

\n

所以GCC不符合C标准。

\n

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

\n