因此,我将问题简化为一个非常简单的程序,其中包含一个空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文件一起(:linkedTest和test。
现在,运行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.cpp
和A.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
对象。