C语言是否像C++一样有一个定义规则?

25

最近,我发现一些情况绝对违反了C++的ODR规则,但在C编译器中仍能正常编译通过。

例如,以下奇怪的情况(与我有关):

源代码1

int var_global=-3;

来源2

#include <stdio.h>
#include <conio.h>

unsigned int var_global;

int main() {    

 printf("%d \n",var_global);
 getch();
 return 0;

}

我得到的打印结果是 -3(即使在源2var_globalunsigned),并且没有关于var_global重新定义的错误。

我知道C和C ++有不同的规则,但我认为它们之间的差异不会像这样大。

我已经谷歌搜索并阅读了很多结果,但没有找到C ++官方的类似结果。

所以问题是:

C是否像C ++一样具有“单一定义规则”?

以及:

它正式叫什么名字?

我需要将其与C ++的规则进行比较,以便更深入地理解两种语言。

p / s:我使用Visual Studio 2010编译上面的代码。


2
“这将绝对违反C++的ODR,但在C编译器中可以编译通过。” 你尝试过用C++编译器编译它吗?有实际的错误信息吗? - n. m.
8
请勿在问题回答后更改问题,这会让回答人看起来像傻瓜。:) 您可以随时[编辑]问题并使用评论请求更多明确信息。 - Sourav Ghosh
抱歉,我已经提到了我编辑的内容,但不知道为什么它没有显示。让我重新编辑一下。 - Van Tr
你把这两个文件链接在一起了吗?怎么链接的?从你的问题中我们可以看出,源1中甚至没有被项目使用。在这里C和C++之间没有区别。所以看起来你只是在C情况下使用了两个完全不同的链接器,并且在链接时出了问题? - Lundin
@Lundin 当然,我在同一个项目中构建了这两个文件。 - Van Tr
显示剩余2条评论
2个回答

19

我认为你要找的是C11标准中第§6.2.7章节,名为“兼容类型和复合类型”,(强调是我的)

 

所有引用同一对象或函数的声明必须具有兼容类型;否则,行为未定义。

关于兼容类型:

 

如果两种类型相同,则它们具有兼容类型。

在你的情况下,intunsigned int不是兼容类型。因此会导致未定义行为

只是为了更清晰地说明,在你的source 2中,unsigned int var_global;是一个声明,它与其他声明(和定义)不匹配,因此这是未定义行为。

话虽如此,像这样的语句:

 printf("%d \n",var_global);

如果类型和格式说明符不匹配,则始终将参数的类型视为int。在这种情况下,会再次引发未定义行为


编辑:

在编辑后,答案是使用-fno-common以获取所需的错误。(我相信您烦扰的是缺少extern)。

引用在线GCC手册中的语句:

-fno-common

在C代码中,控制未初始化全局变量的放置方式。Unix C编译器传统上允许在不同的编译单元中通过将变量放置在公共块中多次定义此类变量。这是由-fcommon指定的行为,并且是大多数目标的GCC的默认值。另一方面,ISO C不需要此行为,并且在某些目标上可能会对变量引用造成速度或代码大小的惩罚。-fno-common选项指定编译器应该将未初始化的全局变量放置在对象文件的数据段中,而不是生成它们作为公共块。这具有如下效果:如果在两个不同的编译中声明了相同的变量(没有extern),则在将它们链接时会发生多重定义错误。在这种情况下,您必须使用-fcommon进行编译。使用-fno-common对于提供更好性能的目标是有用的,或者如果您希望验证程序将在始终将未初始化变量声明此方式的其他系统上运行,则可以使用-fno-common。


我不知道C标准中是否提到了“一个定义规则”的措辞,但是您可以查看附录§J.5.11,多个外部定义

可能会存在对象标识符的多个外部定义,有或没有显式使用关键字extern; 如果定义不符,或者有多个已初始化,则其行为是未定义的。


1
@TrieuTheVan,你没有澄清问题,而是__改变了__问题。 - Sourav Ghosh
我有两个问题,它们用粗体标出,并没有改变任何内容 :). 也许代码会让人感到困惑(我已经修复了),但这两个问题非常清晰明了。 - Van Tr
@TrieuTheVan 这个问题不仅涉及到粗体的行,还包括代码本身。而且为了确保您理解更改此代码中的intunsigned int的影响,对此您应该是明白的,对吧? - Sourav Ghosh
是的,我确实知道将无符号整数更改为整数的影响。我想问的是关于C的定义规则(这些规则不仅适用于变量而且适用于函数,不仅适用于全局而且适用于静态对象)。这就是为什么我要求类似于C++的ODR进行比较(正如我在问题中所说的那样)。 - Van Tr
1
@TrieuTheVan 嗯,根据我的了解,我已经添加了标准引号。如果有其他答案可以更好地阐明这一点,我会期待着得到更多的见解。 :) - Sourav Ghosh
显示剩余3条评论

2
你所看到的与一种定义规则无关。它与%d期望有符号值有关,因此在你的实现中几乎肯定会将其视为带符号值。
然而,这不是你应该依赖的东西。根据C标准7.19.6.1 fprintf函数/9(我引用了C99,但C11在这方面基本相同):
如果任何参数与相应的转换说明符的类型不正确,则行为是未定义的。
由于你使用了未定义的行为,实现可以自由地做任何想做的事情。此外,标准还明确指出,如果(来自附录J):
两个声明相同的对象或函数指定不兼容的类型,则行为是未定义的。
在你的情况下,这两个声明确实指定了相同的对象,因为它们都具有外部链接。
现在你可能认为有符号和无符号整数是兼容的,但你错了:6.2.7 兼容和复合类型6.2.5 类型清楚地表明,有符号和无符号变体不兼容:
如果它们的类型相同,则两种类型具有兼容的类型。 对于每个有符号整数类型,都有一个相应的(但不同的)无符号整数类型(使用关键字unsigned),它使用相同的存储量(包括符号信息)并具有相同的对齐要求。

是的,我知道%d点,但在这种情况下,我关注的是变量被重新定义,并且对于C编译器来说是可以的。 - Van Tr
@sir paxdiablo,我已经重新添加了标签。如果您对标记有异议,请告诉我。谢谢。 :) - Sourav Ghosh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接