C语言中的局部函数声明机制是什么?

3

在gcc中似乎允许本地函数声明,我在这里找到了一个相关的讨论:Is there any use for local function declarations?

然而,我的问题是:它是否符合ISO C标准?如果是,如何解释以下现象,从而使人感到困惑:

int main(void) {
    int f(void);
    f();
}
void g(void) {
    /* g has no idea about f. It seems that the decl is limited within its
     * scope */
    f(); 
}
int f(void) {}

while

int main(void) {
    int f(void);
    f();
}
void f(void); /* error because disagreement with the local declaration (local
             declaration goes beyound its scope?) */
void f(void) { /* definition here */ }

根据C99标准:函数名属于同一命名类别。因此,我们需要讨论作用域机制来解释这个问题。但是如何呢?
实际上,我正在进行编译器课程项目,需要我们实现简化版的C编译器。我试图处理这种情况,但是感到困惑。
编辑:我知道C是面向过程的,并且要求函数名唯一。但是这种局部声明风格使情况变得清晰,很难理解其原则/规则。

1
你应该将它解析为 "{local {function declaration}}",而不是 "{{local function} declaration}"。声明是局部的,而不是函数。 - Kerrek SB
@Determinant 对不起,我误解了你的问题。因此我已经删除了我的答案。在main中的函数声明和它外部的函数声明不共享作用域。然而,你有两个不同类型的同名声明。你只能有一个f函数定义。 - ajay
@ajay 没关系,谢谢你的帮忙 :) - Determinant
@Determinant 我认为问题不在于与本地声明的不一致,因为它们不共享作用域。问题是你不能在main内部和外部调用f,因为只能有一个f的定义,并且它不能匹配两个声明,因为它们具有不同的返回类型。 - ajay
@Determinant 这是完全有可能的,因为我没有看到你代码中的其他问题。你应该展示更多的代码,这样我们才能重现错误。这是完全诊断问题的唯一方法。 - ajay
显示剩余4条评论
3个回答

4

ISO C和C++都允许局部函数声明。在每种情况下,函数声明的作用域在局部作用域结束时结束。然而,函数声明及其定义具有外部链接性,因此它们仍然需要被链接器接受。

在您的示例中:

int main(void) {
 int f(void);
 f();
}
void f(void);
void f(void) {  }

这段代码可以在C或者C++中编译通过。在C中,它应该会连接(通常情况下),但是在C++中,由于“类型安全链接”,它将无法连接。对于int f(void),会有一个未解决的外部引用。
C标准包含以下内容。n1570/S6.7.1/7:
函数标识符的声明具有块作用域,不得具有显式的存储类说明符,除了extern之外。
很明显,允许显式声明局部函数。
n1570 S6.2.2/5关于外部链接:
如果函数标识符的声明没有存储类说明符,则其链接方式与extern存储类说明符一样确定。如果对象的文件作用域标识符的声明没有存储类说明符,则其链接是外部的。
因此,局部函数声明具有外部链接。这是显然的:如果它们具有内部或没有链接,它们就无法链接到任何东西。

我明白了。你的意思是这是一个特殊情况?我很好奇为什么在C99标准文档中找不到任何描述。 - Determinant
谢谢。最后一个问题:在标准中,"函数声明及其定义具有外部链接性"的描述在哪里? - Determinant
抱歉我没有耐心阅读文档,但最终还是弄明白了。谢谢! - Determinant
最近发现一个库,当在C++中编译时,会有条件地将代码段放入命名空间中。C++标准是否指定了本地函数的命名空间?在VS2022中,MSVC似乎会将本地函数解析到全局命名空间中? - shangjiaxuan
@shangjiaxuan:这是一个新问题,需要以这种方式提出。 - david.pfx

0
要修复第二个示例,您需要在主函数中声明具有正确返回类型的f
int main(void) {
    void f(void);  // function declaration
    f(); // calling function
}  // scope of f ends here

void f(void) {} 

这样你就告诉编译器要找什么,然后它确实会找到。希望能帮到你。


1
由于 f 的作用域结束了,它如何与全局声明冲突?我知道 C 是过程导向的,并且要求函数名唯一,这是常识。但是这种局部声明风格会搅乱清晰的情况,让我很难理解它的规则。 - Determinant
在使用函数之前,您需要告诉编译器它是什么类型的函数。您可以在 main 之前放置全局原型,也可以在使用函数之前定义函数本身,或者使用适当的返回类型放置本地声明,这些都是使事情正常工作的合法方法。 - Dabo
顺便问一下,你明白为什么当函数原型与函数定义不相符时会出现错误吗?例如,你有一个int foo(void);原型和char foo(void){}函数。你声明了某些东西,但后来没有定义它。这与本地函数声明完全相同。你声明了返回intf,但实际上并没有这样的f - Dabo
抱歉,但我认为您并没有很清楚地理解我的问题。我要为我的英语表达道歉。 - Determinant

0

本地的extern声明确实像函数外部的声明一样工作,只是范围有限,或者可以说就像本地的static声明一样,只是该实体可以通过链接外部访问。

为什么?嗯,为什么不呢?这只是将声明和extern说明符的一般规则推广到特定上下文的推论。

我无法回忆起曾经(故意)使用过本地的extern函数声明,但有几次我使用了本地的extern变量声明来添加调试变量。 extern声明可以放置在某些高度嵌套的代码中,并将数据传递给另一个TU中的调试打印机。


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