C++自定义类静态实例分步骤编译链接时构造函数被调用两次的原因
为什么静态对象的构造函数会执行两次?
这是个非常典型的C++编译链接陷阱,我来给你拆解清楚背后的逻辑:
先回顾你的场景
你的代码结构是这样的: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(分步编译链接):
运行g++ -c test.cpp A.cpp g++ test.o A.o -o linkedTestlinkedTest时,Inside A()会输出两次。 - 方式2(直接编译链接):
运行g++ test.cpp -o testtest时,Inside A()只输出一次。
核心原因:编译单元与static的作用域
这里的关键要搞懂两个点:
#include的本质:#include "A.cpp"会把A.cpp的所有内容原封不动地插入到test.cpp的对应位置。所以当你编译test.cpp生成test.o时,这个目标文件里已经包含了static A a这个静态对象的定义。- 全局
static的含义:在C++全局作用域中,static修饰的变量/对象是编译单元私有的——也就是说,每个编译单元里的static对象都是独立的,链接器不会对不同编译单元里的同名static对象做合并。
所以当你分步编译时:
test.o里有一份属于自己的static A aA.o里也有一份完全独立的static A a
链接器会把这两个目标文件合并成可执行文件,运行时这两个静态对象都会被初始化,自然构造函数会执行两次。
而直接编译test.cpp时,整个程序只有一个编译单元(test.cpp加上它包含的A.cpp内容),所以只有一份static A a,构造函数只会执行一次。
这是bug吗?
完全不是bug,这是C++标准规定的预期行为。GCC(包括你用的7.5.0版本)的处理完全符合标准。
怎么控制/解决?
最规范也最根本的解决方法是遵守C++的代码分离规范:永远不要#include源文件(.cpp),把类的声明放在头文件(.h/.hpp),实现放在源文件里。重构后的代码示例:
A.hpp(头文件,放类声明):
#pragma once // 防止头文件重复包含 #include <iostream> class A { public: A(); // 只声明构造函数 };
A.cpp(源文件,放实现和静态对象):
#include "A.hpp" // 实现构造函数 A::A() {std::cout<<"Inside A()"<<std::endl;} // 静态对象只在这里定义一次 static A a;
test.cpp:
#include "A.hpp" // 只包含头文件 int main() {}
这样不管你用分步编译还是直接编译,整个程序里只会有一份static A a,运行时构造函数只会执行一次。
如果暂时不想重构代码,那分步编译时不要同时编译test.cpp和A.cpp,只编译test.cpp即可:
g++ test.cpp -o linkedTest
这样结果就和方式2完全一致了。
内容的提问来源于stack exchange,提问作者Hack06




