当在单独的步骤中编译和链接时,用户定义类的C++静态实例会导致对构造函数的双重调用



因此,我将问题简化为一个非常简单的程序,其中包含一个空main((函数和一个非常复杂的类,如下所示。

A.cpp

#include <iostream>
class A {
public:
A() {std::cout<<"Inside A()"<<std::endl;}
};
static A a;

test.cpp

#include "A.cpp"
int main() {}

现在考虑两个选项,将这个简单的程序构建成两个不同的可执行文件:

生成程序#1:

使用以下命令进行编译(从.cpp文件生成.o文件(:g++ -c test.cpp A.cpp

然后使用以下命令链接:g++ test.o A.o -o linkedTest

生成程序#2:

使用以下命令立即编译并链接:g++ test.cpp -o test

因此,在这一点上,我们在源文件旁边有两个程序(与中间.o文件一起(:linkedTesttest

现在,运行test程序(命令./test(,它将只执行类A的构造函数一次,并打印文本"Inside A((">。相反,运行linkedTest程序(命令./linkedTest(,它将执行类A的构造函数两次!

所以我的问题是:为什么会发生这种情况?难道同一个编译器不应该(至少(从同一个源代码中生成同一个程序吗?舞台后面到底发生了什么?如何控制它?这是预期的编译器/链接器行为还是(未知(错误?

任何一位C++大师都能对此有所了解。。。?

供您参考,我的GCC版本是:GCC(Ubuntu 7.5.0-3ubuntu1~18.04(7.5.0

请记住,静态var是按编译单元定义的

在以下情况下:

g++ -c test.cpp A.cpp
g++ test.o A.o -o linkedTest

编译器创建2个对象,每个对象都有自己的静态变量A.

而通过只构建一个对象:

g++ test.cpp -o test

你得到了一个编译单元,也就得到了A.的一个定义

编译test.cppA.cpp时,有两个编译单元,它们都定义了一个名为a的变量。由于该变量被声明为静态,因此这是合法的(否则您会收到关于a被定义两次的错误(,并导致两个自变量被定义为同名。既然你得到了两个变量,你也得到了两次构造函数调用。

当您只定义test.cpp时,就只有一个编译单元,只有一个a,因此只有一个构造函数调用。

附言:将源文件相互包含通常是个坏主意,因为这会导致这样的问题。

#include作为*.cpp文件是不寻常的,通常是个坏主意。

但是,如果像正常情况一样使用头文件,并使用包含它的第二个*.cpp文件,则会得到相同的行为:

// A.hpp:
#ifndef TEST_CLASS_A_HPP
#define TEST_CLASS_A_HPP
#include <iostream>
class A {
public:
A() {std::cout<<"Inside A()"<<std::endl;}
};
static A a;
#endif
// A.cpp:
#include "A.hpp"
// and nothing else!
// test.cpp:
#include "A.cpp"
int main() {}

当以正常方式编译上面的程序时,有两个"翻译单元":一个用于A.cpp,它包括A.hpp中的所有内容,另一个用于test.cpp,它也包括A.hpp中的所有信息。在任何类或函数之外,关键字static表示定义具有"内部链接",因此不能从另一个翻译单元使用,如果另一个翻译单元定义了类似的东西,那么它定义了一个同名的不同对象或函数。因此,是的,程序已经并自动创建了两个对象,它们都命名为a,类型为A

您的原始程序由A.cpp和test.cpp组成,其中包括A.cpp,类似地有两个翻译单元,每个单元都有自己的对象a,类型为A。刚刚编译test.cpp的版本只有一个翻译单元和一个a对象。

最新更新