【问题标题】:Accordance of linkage between declaration and definition声明和定义之间的联系的一致性
【发布时间】:2014-10-08 13:10:17
【问题描述】:

我想知道下面的 C sn-p 是否正确,其中 f 的定义没有重复 fstatic 链接,是正确的:

static int f(int);

int f(int x) { return x; }

Clang 不会发出任何警告。我阅读了 C11 标准的第 6.7.1 条,但没有找到问题的答案。

可以想象更多类似的问题,例如下面的 t1.c 和 t2.c,如果答案足够笼统以适用于其中一些问题,那就太好了,但我只是真的很担心关于上面的第一个例子。

~ $ cat t1.c
static int f(int);

int f(int);

int f(int x) { return x; }
~ $ clang -c -std=c99 -pedantic t1.c
~ $ nm t1.o
warning: /Applications/Xcode.app/…/bin/nm: no name list
~ $ cat t2.c
int f(int);

static int f(int);

int f(int x) { return x; }
~ $ clang -c -std=c99 -pedantic t2.c
t2.c:3:12: error: static declaration of 'f' follows non-static declaration
static int f(int);
           ^
t2.c:1:5: note: previous declaration is here
int f(int);
    ^
1 error generated.

【问题讨论】:

    标签: c language-lawyer c11


    【解决方案1】:

    链接的规则有点混乱,函数和对象的规则不同。简而言之,规则如下:

    • 第一个声明决定了链接。
    • static 表示内部链接。
    • extern 表示已声明的链接,如果未声明,则为外部链接。
    • 如果两者都没有给出,则函数与 extern 相同,对象标识符的外部链接(在同一翻译单元中定义)。

    所以,这是有效的:

    static int f(int); // Linkage of f is internal.
    
    int f(int); // Same as next line.
    
    extern int f(int); // Linkage as declared before, thus internal.
    
    int f(int x) { return x; }
    

    另一方面,这是未定义的行为(参见 C11 (n1570) 6.2.2 p7):

    int f(int); // Same as if extern was given, no declaration visible,
                // so linkage is external.
    
    static int f(int); // UB, already declared with external linkage.
    
    int f(int x) { return x; } // Would be fine if either of the above
                               // declarations was removed.
    

    C11 6.2.2 中涵盖了大部分内容。来自 N1570 草案:

    (3) 如果对象或函数的文件范围标识符的声明包含存储类说明符static,则该标识符具有内部链接。 30)

    (4) 对于使用存储类说明符 extern 在该标识符的先前声明可见的范围内声明的标识符31),如果先前声明指定内部或外部链接,后面声明标识符的链接与前面声明中指定的链接相同。如果前面的声明不可见,或者前面的声明没有指定链接,则标识符具有外部链接。

    (5) 如果函数标识符的声明没有存储类说明符,则其链接的确定与使用存储类说明符extern 声明时完全相同。如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的。

    30) 只有在文件范围内,函数声明才能包含存储类说明符 static;见 6.7.1。
    31) 如 6.2.1 所述,后面的声明可能会隐藏前面的声明。

    【讨论】:

    • 我不明白为什么第二个 sn-p 需要诊断。与@BlueMoon 所引用的相反,似乎这个“唯一”是UB。
    • @JensGustedt:谢谢,我想知道,如果蓝月亮的答案中提到的 UB 是违反约束的,那么它应该如何发生,这就是答案。我的理解是否正确,通常,拒绝在 UB 上编译(在语句中)意味着编译器必须能够确定有问题的代码是可访问的,但这不适用于这里,因为我们正在处理 (文件范围)声明?
    • 是的,这是未执行的代码,因此可达性没有多大意义:) 这里的 UB 意味着整个程序从一开始就有 UB。可能基本上是因为标识符的链接可能出错了。尽管如此,由于 UB 有点蹩脚,这在编译时很容易检测到,所以我认为违反约束是有必要的。
    • @JensGustedt:谢谢。 C89 rationale 对此有话要说,也许 UB 的原因是允许在现有实现中进行扩展/较少更改,而无需对具有明确定义的含义(无论应该是什么)的实现进行诊断并依赖如果实现者足够复杂以中止编译而不是格式化硬盘驱动器...
    【解决方案2】:

    根据C11、6.2.2、7,它们都是未定义的行为

    如果在一个翻译单元中,相同的标识符出现在两个 内部和外部链接,行为未定义。

    函数也是一个标识符,并且默认情况下(没有任何限定符,如static)具有外部链接。

    C11,6.2.1 标识符的范围

    1 一个标识符可以表示一个对象;一个函数;结构、联合或枚举的标签或成员;一种 类型定义名称;标签名称;宏名;或宏参数。这 相同的标识符可以在不同的点表示不同的实体 该程序。枚举的成员称为枚举 持续的。宏名称和宏参数不再考虑 在这里,因为在程序翻译的语义阶段之前,任何 源文件中出现的宏名称被替换为 预处理构成其宏定义的标记序列。

    【讨论】:

    • 抱歉,我将“已接受”标签移至另一个更符合 Clang 行为的答案。我仍然感谢您迅速指出解释将在 6.2.2 中找到。
    • np,这是您的选择。我确实同意 mafso 更好,因为它涵盖了声明顺序很重要时的额外差异(以及 +1 到 mafso!)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多