代码之家  ›  专栏  ›  技术社区  ›  Petr Skocik

兼容类型并忽略C类型系统中的顶级限定符

  •  2
  • Petr Skocik  · 技术社区  · 6 年前

    这是一个多部分的问题。

    我一直在努力理解C型系统。首先是C标准 经常提到“兼容类型”,所以我试着去理解它。 这个定义似乎很分散,但我发现:

    6.2.7兼容类型和复合类型1如果类型相同,则两种类型具有兼容类型。确定的其他规则 6.7.2中描述了两种类型是否兼容 6.7.3中的类型限定符和6.7.6中的 声明人。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
    

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

    在确定函数兼容性的上下文中,是否忽略原型类型列表中类型的顶级限定符?

    谢谢

    2 回复  |  直到 6 年前
        1
  •  3
  •   ov2k    5 年前

    事情稍微复杂一些,因为 _Generic() 有一些额外的规则,因为您似乎在叮当声中遇到了一个bug,现在已经修复了。

    C18(6.5.1.1第2段)对通用选择增加了一些要求:

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

    脚注93规定:

    左值转换删除类型限定符。

    为您的 _Generic((int const){0}, ...) 例如,FreeBSD clang 6.0.1版报告 int

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

        2
  •  1
  •   Antti Haapala -- Слава Україні    5 年前

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

    struct foo *
    

    在一个翻译单元中,与

    结构foo*
    

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

    同样,a类型及其 typedef 相互兼容。

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

    extern int a[];
    

    在另一个

    int a[10];
    

    它们是兼容的类型。

    C11 6.7.6p6 :

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

    此外,VLA类型可以是与静态维度数组兼容的类型-如果元素类型相同,则视为始终兼容,但如果维度在需要时实际不匹配,则行为将是未定义的。


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

    (const int){0} 是否创建类型为的左值 const int (复合文字),但随后的左值转换应删除类型限定符,结果应为 int 。事实上 _通用型 选择不应能够选择以下类型 const -合格,所以我认为 compilers should issue a warning for even having the const qualifier there