【问题标题】:Is having global variables in common blocks an undefined behaviour?在公共块中有全局变量是一种未定义的行为吗?
【发布时间】:2021-05-18 13:21:39
【问题描述】:

0.c

int i = 5;

int main(){
 return i;
}

1.c

int i;

上面使用gcc 0.c 1.c 编译良好,没有关于multiple definitions 的任何链接错误。原因是i 生成为common blocks (-fcommon which is the default behaviour in gcc)。 正确的方法是使用此处缺少的 extern 关键字。

我一直在网上搜索这是否是未定义的行为,有些帖子说是,有些人说不是,这非常令人困惑:

是UB

Is having multiple tentative definitions in separate files undefined behaviour?

Why can I define a variable twice in C?

How do I use extern to share variables between source files?

http://port70.net/~nsz/c/c11/n1570.html#J.2

使用了具有外部链接的标识符,但在程序中不存在该标识符的确切一个外部定义,或者未使用该标识符并且该标识符存在多个外部定义(6.9)。

不是 UB

Global variables and the .data section

Defining an extern variable in multiple files in C

Does C have One Definition Rule like C++?

Look for -fno-common: https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Code-Gen-Options.html

那么它是哪一个?使用-fcommon 是少数几个允许使用multiple definition 并且编译器会为您解决的地方之一?还是还是UB?

【问题讨论】:

  • 根据标准这是未定义的,但在 gcc 上定义了实现。这不是唯一一个由于扩展而导致未定义的“工作”的例子。
  • 答案stackoverflow.com/questions/67270121/… 解释:.... If a program disobeys this rule, the C standard does not define the behavior (C 2018 4 2). Instead, we let the compiler and the linker define the behavior. 和整个段落。
  • 尝试用gcc 0.c 1.c -pedantic-errors -Wall -Wextra重新编译,如果这是一个扩展,它可能会给你一个错误/警告。 (未测试)
  • 不管怎样,MSVC 的链接和 LLVM 的 lld 都无法链接并引发重复符号错误。当没有使用 -extern 引用非静态全局变量时,Clang 也会发出警告。
  • 恭喜你提出了很好的问题。

标签: c


【解决方案1】:

根据C标准分析代码

最新 C 标准的第 6.9/5 节对此进行了介绍:

语义

外部定义是一个外部声明,它也是一个函数(内联定义除外)或对象的定义。如果用外部链接声明的标识符在表达式中使用(不是作为sizeof_Alignof 运算符的操作数的一部分,其结果是整数常量),则在整个程序的某处应恰好有一个外部定义为了 标识符;否则,不得超过一个。

术语“外部定义”不应与“外部链接”或extern 关键字混淆,它们是完全不同的概念,但拼写相似。

“外部定义”是指不是暂定的定义,也不在函数内部。

关于暂定定义,这在 6.9.2/2 中涵盖:

具有文件范围的对象的标识符声明没有初始化程序,没有存储类说明符或具有存储类说明符 static ,构成暂定定义。如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元完全相同包含该标识符的文件范围声明,复合类型截至翻译单元的末尾,初始值设定项等于0

因此,在您的文件 1.c 中,根据 6.9.2/2,其行为与它所说的 int i = 0; 完全相同。这将是一个外部定义。这意味着 0.c1.c 都表现得好像它们有外部定义,这违反了规则 6.9/5 规定的外部定义不得超过一个。

违反语义规则意味着行为未定义,无需诊断。

解释“未定义行为”的含义

另请参阅: Undefined, unspecified and implementation-defined behavior

如果不清楚,C 标准说“行为未定义”意味着 C 标准没有定义行为。基于不同的符合实现的相同代码(或基于相同符合实现的重新构建)可能表现不同,包括拒绝程序、接受程序或您可能想象的任何其他结果。

(注意 - 某些程序的行为的定义性取决于运行时条件;这些程序不能在编译时被拒绝,并且必须按照指定的方式运行,除非出现导致行为未定义的条件。但是不适用于此问题中的程序,因为所有可能的执行都会违反 6.9/5)。

对于 C 标准未定义行为的情况,编译器供应商可能会或可能不会提供稳定和/或记录的行为。

对于您问题中的代码,编译器供应商提供可靠行为是很常见的(哈哈);这记录在标准的非规范性附录 J.5.11 中:

J.5 常用扩展

J.5.11 多个外部定义 1 一个对象的标识符可能有多个外部定义,无论是否显式使用关键字extern;如果定义不一致,或者初始化了多个,则行为未定义 (6.9.2)。

如果提供了-fcommon 开关,gcc 编译器似乎实现了这个扩展,如果提供了-fno-common,则禁用它(并且默认设置可能因编译器版本而异)。


脚注:对于 C 标准未定义的行为,我有意避免使用“已定义”一词,因为在我看来,这是造成 OP 混淆的原因之一。

【讨论】:

    猜你喜欢
    • 2019-12-11
    • 2020-04-05
    • 2015-01-02
    • 1970-01-01
    • 2020-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多