【问题标题】:Compatible types and ignoring top-level qualifiers in the C type system兼容类型并忽略 C 类型系统中的顶级限定符
【发布时间】:2018-03-25 18:37:18
【问题描述】:

这是一个多部分的问题。

我一直在尝试理解 C 类型系统。首先是C标准 经常提到“兼容类型”这个词,所以我试图理解这一点。 定义似乎很分散,但根据我的发现:

6.2.7 兼容类型和复合类型 1 两种类型如果它们的类型相同,则它们具有兼容类型。确定的附加规则 6.7.2 中描述了两种类型是否兼容 说明符,在 6.7.3 中用于类型限定符,在 6.7.6 中用于 declarators.55) 此外,两种结构、联合或枚举类型 如果它们的标签在单独的翻译单元中声明是兼容的 并且成员满足以下要求:如果一个被声明 一个标签,另一个应该用相同的标签声明。如果两者都是 在各自翻译单元内的任何地方完成,然后 以下附加要求适用:应有一对一 他们的成员之间的对应关系,使得每一对 相应的成员被声明为兼容的类型;如果一个 该对的成员使用对齐说明符声明,另一个 用等效的对齐说明符声明;如果一个成员 一对用名称声明,另一个用 一样的名字。对于两个结构,应声明相应的成员 以相同的顺序。对于两个结构或联合,对应 位域应具有相同的宽度。对于两个枚举, 相应的成员应具有相同的值。

REFS:
    6.7.2  short == short int == signed short == signed short int, etc.
    6.7.3
        10) For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.
    6.7.6
        1.2)
            For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
        2.6)
For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.

在我看来

  1. 如果它们的所有完整部分相同,则这两种类型是兼容的。
  2. (由于 1.)“完全兼容类型”实际上意味着“相同类型”。

首先,我想问一下我的解释是否准确。

其次,标准中的_Generic 选择是根据“兼容类型”的概念定义的:

6.5.1.1 类属选择 2 类属选择应具有不超过一个默认类属关联。泛型中的类型名称 关联应指定一个完整的对象类型,而不是一个变量 修改型。同一个泛型中没有两个泛型关联 选择应指定兼容的类型。控制表达式 泛型选择的类型应与最多一个兼容的类型 在其通用关联列表中命名的类型。如果一个泛型 选择没有默认的通用关联,它的控制 表达式应具有与其中一种类型完全兼容的类型 在其通用关联列表中命名。

但编译器似乎对顶级限定符有不同的解释:

$ $CC -x c -include stdio.h - <<<'int main(){puts( _Generic((int const){0}, int:"int", int const: "int const")); }' && ./a.out      #int with gcc, and int const with clang

在我看来,铿锵的解释是正确的,但令人困惑的是

$ $CC -x c -include stdio.h - <<<'int main(){puts( _Generic((int const)0, int:"int", int const: "int const")); }' && ./a.out        

"int" 甚至在叮当声。

所以我的第二个问题是,将(int const)0 解释为int 类型和(int const){0} 类型为int const 的标准的基础是什么?

最后,在我的所有编译器(tcc、gcc、clang)中,原型类型列表中的所有类型似乎都忽略了顶级限定符 在确定函数或函数指针之间的兼容性时:

for CC in tcc gcc clang; do echo CC=$CC; $CC -x c  - <<<'int main(){ static void (*f)(int*), (*g)(int * restrict const volatile);  f=g; }' ; done #no complaints

但我在标准中找不到任何提及,所以我的最后一个问题是:

在确定功能兼容性的上下文中,忽略原型类型列表中类型的顶级限定符是否符合标准?

谢谢。

【问题讨论】:

  • 而接受的答案是错误的:D

标签: c generics language-lawyer type-systems


【解决方案1】:

事情稍微复杂一些,因为_Generic() 有一些额外的规则,而且你似乎遇到了一个现在已经修复的clang 错误。

C18(6.5.1.1 第 2 段)对泛型选择添加了一些要求:

控制表达式的类型是表达式的类型,就好像它经历了左值转换,93) 数组到指针的转换,或函数到指针的转换。

脚注 93 指出:

左值转换丢弃类型限定符。

对于您的 _Generic((int const){0}, ...) 示例,FreeBSD clang 版本 6.0.1 报告 int

基本上,由于控制表达式的左值转换,泛型选择不是探索兼容类型概念的好方法,因为它与类型限定符相关。

【讨论】:

  • 感谢您将此 Q/A 挖掘到活动列表中:D
【解决方案2】:

兼容类型并不意味着它们必须在所有用途中都必须是完全相同的类型。通知

struct foo *

在一个翻译单元中兼容

struct foo *

如果两者都不是指向完整类型的指针。但是,如果 foo 的成员即使在声明之后也被定义,那么定义必须匹配,否则之前的指针将不兼容!

同样,一个类型和它的typedef 是相互兼容的。

但兼容性并不要求类型相同:数组可以有不完整类型和完整类型,并且它们彼此兼容。您可以在一个翻译单元中声明

extern int a[];

在另一个

int a[10];

它们是兼容的类型。

C11 6.7.6p6:

对于要兼容的两个数组类型,两者都应具有兼容的元素类型,并且如果两个大小说明符都存在,并且都是整数常量表达式,那么两个大小说明符应具有相同的常量值。如果在要求它们兼容的上下文中使用这两种数组类型,则如果这两个大小说明符的计算结果不相等,则为未定义行为。

此外,VLA 类型可以是与静态维度数组兼容的类型 - 如果元素类型相同,则始终认为它是兼容的,但如果维度实际上不匹配,则行为将不确定必填。


至于_Generic,Clang 肯定是这里的错。事实上,Defect report 481 已经解决了这个问题,并且认为 Clang 始终是错误的,而 GCC 是正确的;该标准针对 C18 as noted by ov2k 进行了修订。另一种情况参见this Q/A,这一次是由于 Clang 没有将数组左值转换为指针类型。

(const int){0} 确实创建了一个const int 类型的左值(复合文字),但随后的左值转换应该删除类型限定符,结果应该是int。事实上_Generic 选择应该不能选择任何const-qualified 的类型,所以我认为compilers should issue a warning for even having the const qualifier there

【讨论】:

  • 我认为你编辑得太多了!很好地解释了复合文字 (const int) {0} 和演员表 (const int) 0 之间的区别。即使最初的动机因为_Generic() 而变得古怪,那也是被问到的事情之一。
猜你喜欢
  • 1970-01-01
  • 2013-08-15
  • 2021-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-14
  • 1970-01-01
相关资源
最近更新 更多