为什么函数需要在使用前声明?

发布于 2024-10-14 03:57:48 字数 552 浏览 7 评论 0原文

当阅读这个问题的一些答案时,我开始想知道为什么编译器实际上这样做 需要在第一次遇到某个函数时了解它。在解析收集其中声明的所有符号的编译单元时添加额外的传递不是很简单,这样它们的声明和使用顺序就不再重要了吗?

有人可能会说,在使用函数之前声明函数当然是一种很好的风格,但我想知道,是否还有其他原因说明这在 C++ 中是强制性的?

编辑 - 举例说明:假设您必须在头文件中内联定义函数。这两个函数相互调用(可能是递归树遍历,其中树的奇数层和偶数层的处理方式不同)。解决此问题的唯一方法是在其中一个函数之前对另一个函数进行前向声明。

一个更常见的示例(尽管是类,而不是函数)是具有私有构造函数和工厂的类。工厂需要知道该类才能创建它的实例,并且该类需要知道该工厂才能进行 friend 声明。

如果这是从前的要求,为什么它没有在某个时候被删除?它不会破坏现有的代码,不是吗?

When reading through some answers to this question, I started wondering why the compiler actually does need to know about a function when it first encounters it. Wouldn't it be simple to just add an extra pass when parsing a compilation unit that collects all symbols declared within, so that the order in which they are declared and used does not matter anymore?

One could argue, that declaring functions before they are used certainly is good style, but I am wondering, is there are any other reason why this is mandatory in C++?

Edit - An example to illustrate: Suppose you have to functions that are defined inline in a header file. These two function call each other (maybe a recursive tree traversal, where odd and even layers of the tree are handled differently). The only way to resolve this would be to make a forward declaration of one of the functions before the other.

A more common example (though with classes, not functions) is the case of classes with private constructors and factories. The factory needs to know the class in order to create instances of it, and the class needs to know the factory for the friend declaration.

If this is requirement is from the olden days, why was it not removed at some point? It would not break existing code, would it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

旧伤慢歌 2024-10-21 03:57:48

您建议如何解决未声明在不同翻译单元中定义的标识符

C++没有模块概念,但继承自C,具有单独的翻译。C++编译器将自行编译每个翻译单元,根本不知道其他翻译单元的任何信息。 (除了export打破了这一点,这可能就是为什么它,遗憾的是,从未起飞。)
头文件通常是放置在其他翻译单元中定义的标识符声明的地方,实际上是将相同声明放入不同翻译单元的一种非常笨拙的方法。它们不会让编译器知道还有其他翻译单元中定义了标识符。

编辑您的其他示例:
由于所有文本包含而不是正确的模块概念,对于 C++ 来说,编译已经花费了非常长的时间,因此需要另一个编译过程(编译已经被分成几个过程,并非所有过程都可以优化和合并,IIRC)会恶化已经是很糟糕的问题了。更改此设置可能会改变某些情况下的重载解析,从而破坏现有代码。

请注意,C++ 确实需要额外的传递来解析类定义,因为解析在类定义中内联定义的成员函数就好像它们是在类定义后面定义的一样。然而,这是在想到带有类的 C 时就决定的,因此没有现有的代码库可以破坏。

How do you propose to resolve undeclared identifiers that are defined in a different translation unit?

C++ has no module concept, but has separate translation as an inheritance from C. A C++ compiler will compile each translation unit by itself, not knowing anything about other translation units at all. (Except that export broke this, which is probably why it, sadly, never took off.)
Header files, which is where you usually put declarations of identifiers which are defined in other translation units, actually are just a very clumsy way of slipping the same declarations into different translation units. They will not make the compiler aware of there being other translation units with identifiers being defined in them.

Edit re your additional examples:
With all the textual inclusion instead of a proper module concept, compilation already takes agonizingly long for C++, so requiring another compilation pass (where compilation already is split into several passes, not all of which can be optimized and merged, IIRC) would worsen an already bad problem. And changing this would probably alter overload resolution in some scenarios and thus break existing code.

Note that C++ does require an additional pass for parsing class definitions, since member functions defined inline in the class definition are parsed as if they were defined right behind the class definition. However, this was decided when C with Classes was thought up, so there was no existing code base to break.

蹲在坟头点根烟 2024-10-21 03:57:48

历史上 C89 允许您这样做。编译器第一次看到函数的使用并且没有预定义的原型时,它会“创建”与函数的使用相匹配的原型。

当 C++ 决定向编译器添加严格的类型检查时,就决定现在需要原型。此外,C++ 继承了 C 的单遍编译,因此它无法添加第二遍来解析所有符号。

Historically C89 let you do this. The first time the compiler saw a use of a function and it didn't have a predefined prototype, it "created" a prototype that matched the use of the function.

When C++ decided to add strict typechecking to the compiler, it was decided that prototypes were now required. Also, C++ inherited the single-pass compilation from C, so it couldn't add a second pass to resolved all symbols.

醉酒的小男人 2024-10-21 03:57:48

因为 C 和 C++ 是古老语言。早期的编译器没有大量内存,因此这些语言的设计目的是使编译器可以从上到下读取文件,而不必将文件视为一个整体。

Because C and C++ are old languages. Early compilers didn't have a lot of memory, so these languages were designed so a compiler can just read the file from top to bottom, without having to consider the file as a whole.

横笛休吹塞上声 2024-10-21 03:57:48

我认为有两个原因:

  • 它使解析变得容易。无需额外通行证。
  • 它还定义了范围;符号/名称​​仅在其声明后才可用。意味着,如果我声明一个全局变量int g_count;,则该行之后的代码可以使用它,但不能使用该行之前的代码!全局函数的参数相同。

作为示例,请考虑以下代码:

void g(double)
{
    cout << "void g(double)" << endl;
}
void f()
{
    g(int());//this calls g(double) - because that is what is visible here
}
void g(int)
{
    cout << "void g(int)" << endl;
}
int main()
{
    f();
    g(int());//calls g(int) - because that is what is the best match!
}

输出:

无效 g(双精度)
无效 g(int)

查看 ideone 的输出: http://www.ideone.com/EsK4A

I think of two reasons:

  • It makes the parsing easy. No extra pass needed.
  • It also defines scope; symbols/names are available only after its declaration. Means, if I declare a global variable int g_count;, the later code after this line can use it, but not the code before the line! Same argument for global functions.

As an example, consider this code:

void g(double)
{
    cout << "void g(double)" << endl;
}
void f()
{
    g(int());//this calls g(double) - because that is what is visible here
}
void g(int)
{
    cout << "void g(int)" << endl;
}
int main()
{
    f();
    g(int());//calls g(int) - because that is what is the best match!
}

Output:

void g(double)
void g(int)

See the output at ideone : http://www.ideone.com/EsK4A

污味仙女 2024-10-21 03:57:48

主要原因是使编译过程尽可能高效。如果您添加额外的通行证,则会增加时间和存储空间。请记住,C++ 是在四核处理器时代之前开发的:)

The main reason will be to make the compilation process as efficient as possible. If you add an extra pass you're adding both time and storage. Remember that C++ was developed back before the time of Quad Core Processors :)

(り薆情海 2024-10-21 03:57:48

C 编程语言的设计使得编译器可以实现为一次性编译器 。在这样的编译器中,每个编译阶段仅执行一次。在这样的编译器中,您无法引用稍后在源文件中定义的实体。

此外,在 C 中,编译器一次仅解释一个编译单元(通常是一个 .c 文件和所有包含的 .h 文件)。因此,您需要一种机制来引用另一个编译单元中定义的函数。

之所以做出允许一次性编译并能够将项目拆分为小型编译单元的决定,是因为当时可用的内存和处理能力非常紧张。允许前向声明可以通过单个功能轻松解决问题。

C++ 语言源自 C 并继承了它的功能(因为它希望尽可能与 C 兼容以简化过渡)。

The C programming language was designed so that the compiler could be implemented as a one-pass compiler. In such a compiler, each compilation phase is only executed once. In such a compiler you cannot referrer to an entity that is defined later in the source file.

Moreover, in C, the compiler only interpret a single compilation unit (generally a .c file and all the included .h files) at a time. So you needed a mechanism to referrer to a function defined in another compilation unit.

The decision to allow one-pass compiler and to be able to split a project in small compilation unit was taken because at the time the memory and the processing power available was really tight. And allowing forward-declaration could easily solve the issue with a single feature.

The C++ language was derived from C and inherited the feature from it (as it wanted to be as compatible with C as possible to ease the transition).

ζ澈沫 2024-10-21 03:57:48

我猜想是因为 C 语言相当古老,而且在设计 C 语言时,高效编译是一个问题,因为 CPU 速度慢得多。

I guess because C is quite old and at the time C was designed efficient compilation was a problem because CPUs were much slower.

溺深海 2024-10-21 03:57:48

由于 C++ 是静态语言,因此编译器需要检查值的类型是否与函数参数中期望的类型兼容。当然,如果您不知道函数签名,则无法进行此类检查,从而违背了静态编译器的目的。但是,既然您拥有 C++ 银质徽章,我想您已经知道这一点了。

C++ 语言规范的制定是正确的,因为设计者不想强制使用多遍编译器,因为当时的硬件速度不如当今可用的编译器。最后,我认为,如果今天设计 C++,这种强加将会消失,但那时,我们将拥有另一种语言:-)。

Since C++ is a static language, the compiler needs to check if values' type is compatible with the type expected in the function's parameters. Of course, if you don't know the function signature, you can't do this kind of checks, thus defying the purpose of a static compiler. But, since you have a silver badge in C++, I think you already know this.

The C++ language specs were made right because the designer didn't want to force a multi-pass compiler, when hardware was not as fast as the one available today. In the end, I think that, if C++ was designed today, this imposition would go away but then, we would have another language :-).

音栖息无 2024-10-21 03:57:48

即使在 C99 中(与 C89 相比,您可以拥有隐式声明的函数),这一点也是强制性的,最大原因之一是隐式声明非常容易出错。考虑以下代码:

第一个文件:

#include <stdio.h>
void doSomething(double x, double y)
{
    printf("%g %g\n",x,y);
}

第二个文件:

int main()
{
    doSomething(12345,67890);
    return 0;
}

该程序是语法上有效的* C89 程序。您可以使用此命令使用 GCC 编译它(假设源文件名为 test.ctest0.c):

gcc -std=c89 -pedantic-errors test.c test0.c -o test

为什么它会打印一些奇怪的东西(至少在 linux 上) -x86 和 linux-amd64)?你能一眼看出代码中的问题吗?现在尝试在命令行中将 c89 替换为 c99 — 编译器将立即通知您错误。

与 C++ 相同。但在 C++ 中,实际上还有其他需要函数声明的重要原因,它们在其他答案中进行了讨论。

* 但有未定义的行为

One of the biggest reasons why this was made mandatory even in C99 (compared to C89, where you could have implicitly-declared functions) is that implicit declarations are very error-prone. Consider the following code:

First file:

#include <stdio.h>
void doSomething(double x, double y)
{
    printf("%g %g\n",x,y);
}

Second file:

int main()
{
    doSomething(12345,67890);
    return 0;
}

This program is a syntactically valid* C89 program. You can compile it with GCC using this command (assuming the source files are named test.c and test0.c):

gcc -std=c89 -pedantic-errors test.c test0.c -o test

Why does it print something strange (at least on linux-x86 and linux-amd64)? Can you spot the problem in the code at a glance? Now try replacing c89 with c99 in the command line — and you'll be immediately notified about your mistake by the compiler.

Same with C++. But in C++ there're actually other important reasons why function declarations are needed, they are discussed in other answers.

* But has undefined behavior

冷默言语 2024-10-21 03:57:48

不过,有时您可以在声明函数之前使用该函数(严格用词:“之前”是关于读取程序源代码的顺序)——在类内部!:(

class A {
public:
  static void foo(void) {
    bar();
  }
private:
  static void bar(void) {
    return;
  }
};

int main() {
  A::foo();
  return 0;
}

将类更改为根据我的测试,命名空间不起作用。)

这可能是因为编译器实际上将类内部的成员函数定义放在类声明之后,正如有人在答案中指出的那样。

相同的方法可以应用于整个源文件:首先,删除除声明之外的所有内容,然后处理推迟的所有内容。 (要么是两遍编译器,要么是足够大的内存来保存推迟的源代码。)

哈哈!因此,他们认为整个源文件太大而无法保存在内存中,但是具有函数定义的单个类不会:它们可以允许整个类坐在内存中等待声明被过滤掉(或者对类的源代码进行第二遍)!

Still, you can have a use of a function before it is declared sometimes (to be strict in the wording: "before" is about the order in which the program source is read) -- inside a class!:

class A {
public:
  static void foo(void) {
    bar();
  }
private:
  static void bar(void) {
    return;
  }
};

int main() {
  A::foo();
  return 0;
}

(Changing the class to a namespace doesn't work, per my tests.)

That's probably because the compiler actually puts the member-function definitions from inside the class right after the class declaration, as someone has pointed it out here in the answers.

The same approach could be applied to the whole source file: first, drop everything but declaration, then handle everything postponed. (Either a two-pass compiler, or large enough memory to hold the postponed source code.)

Haha! So, they thought a whole source file would be too large to hold in the memory, but a single class with function definitions wouldn't: they can allow for a whole class to sit in the memory and wait until the declaration is filtered out (or do a 2nd pass for the source code of classes)!

当梦初醒 2024-10-21 03:57:48

我记得在 Unix 和 Linux 中,有 GlobalLocal。在您自己的环境中,本地适用于函数,但不适用于Global(system)。您必须声明函数Global

I remember with Unix and Linux, you have Global and Local. Within your own environment local works for functions, but does not work for Global(system). You must declare the function Global.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文