在C语言中,临时定义的原理是什么?

30

考虑下面这个程序。它会产生编译错误吗?

#include <stdio.h>
int s=5;
int s;
int main(void)
{
     printf("%d",s);
}
乍一看,似乎编译器会给出变量重定义错误,但根据C标准,该程序是完全有效的。(在此处查看实时演示http://ideone.com/Xyo5SY)。
试探性定义是指没有存储类说明符和初始化程序的任何外部数据声明。
C99 6.9.2/2
引用:

没有初始化程序且没有存储类说明符或具有静态存储类说明符的文件范围对象的标识符的声明构成试探性定义。如果一个翻译单元包含一个或多个标识符的试探性定义,并且翻译单元不包含该标识符的外部定义,则行为就像翻译单元包含该标识符的文件范围声明一样,它的综合类型与翻译单元结束时相同,初始化程序等于0。

我的问题是,允许试探性定义的理由是什么?C中是否有这种使用方式?为什么C允许试探性定义?

2
我认为没有任何有价值的理由,因为C++本身从来没有这样做过,其他许多编程语言也是如此。 - edmz
@edmz C++ 不是 C,即使在其具有类和 K&R C 表单的预标准 C 中,这两种语言从未兼容过,因此,在 ANSI C 标准的讨论中,C++ 没有任何地位。 延迟定义的原因与您仍然可以使用 K&R 语法定义 C 函数(main(c,v)int c; char ** v; {…})相同:向后兼容性。 实际向后兼容性。换句话说,您可以通过现代编译器运行自 1973 年以来没有被修改过的 C 代码库,并且它仍然可以编译。 - Braden Best
2个回答

17
Tentative definitions是一种桥接C89之前存在的不兼容模型的方法。这在C99 rationale第6.9.2节“External object definitions”中有所涵盖。该节中提到:“在C90之前,实现关于具有内部链接的前向引用标识符(参见§6.2.2)的规定差异很大。C89委员会发明了“tentative definition”的概念来处理这种情况。tentative definition是一种可能是定义也可能不是定义的声明:如果在翻译单元中稍后找到了实际定义,则tentative definition仅作为声明。否则,tentative definition就作为实际定义。出于一致性考虑,相同的规则适用于具有外部链接的标识符,尽管它们并不严格必要。” C99 rationale的第6.2.2节也提到了这一点。

用于具有外部链接的对象的定义模型是C89标准化问题中的一个重要问题。基本问题是决定哪些对象声明定义了对象的存储,哪些仅引用现有对象。相关问题是是否允许多个存储定义,或者只允许一个。在C89之前的实现至少展示了四种不同的模型,按限制程度递增的顺序列在此处:


1
你是如何如此了解和理解C和C++标准的?你真的很聪明。 - Destructor
2
@PravasiMeet 我知道这个非常好,因为我花了很多时间阅读标准、相关文档和 SO 上的问题。这只是关于实践和经验。你拥有的经验越丰富,你就会遇到越有趣的问题要解决,然后它就会不断地累积下去。 - Shafik Yaghmour
我认为第二个引用并不是很相关。试探性定义是一个仅限于一个翻译单元的特性。它是一个纯粹的编译器特性,没有涉及链接器。它与外部链接和相应的Ref/Def模型无关。我没有看到6.2.2中的原理在试探性定义和Ref/Def模型之间建立任何联系。 - AnT stands with Russia

6
这里有一个例子,说明了它的实用性:

void (*a)();

void bar();
void foo()
{
    a = bar;
}

static void (*a)() = foo;

/* ... code that uses a ... */

关键点在于foo的定义必须引用a,而a的定义必须引用foo。类似的带有初始化结构的例子也应该是可行的。

3
在这个特定情况下,你可以在第一行加上 "extern"(使它成为声明),从而避免需要一个试探性声明。如果你想让 a 成为 static (文件作用域),那么你真正需要试探性声明。 - Chris Dodd
@ChrisDodd:是的,我错过了那个。我会改正的。谢谢。 - R.. GitHub STOP HELPING ICE
4
避免这种情况的另一种方法是void foo(); void (*a)() = foo; - M.M
error: static declaration of 'a' follows non-static declaration - pmor
可能还有一个有用的例子:struct x x; struct x { char x[ sizeof( &x ) ]; };。注意:MSVC无法编译此代码。注意:MSVC团队不会修复它。 - pmor

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