You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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 linkedTest
    
    运行linkedTest时,Inside A()会输出两次。
  • 方式2(直接编译链接)
    g++ test.cpp -o test
    
    运行test时,Inside A()只输出一次。

核心原因:编译单元与static的作用域

这里的关键要搞懂两个点:

  1. #include的本质#include "A.cpp"会把A.cpp的所有内容原封不动地插入到test.cpp的对应位置。所以当你编译test.cpp生成test.o时,这个目标文件里已经包含了static A a这个静态对象的定义。
  2. 全局static的含义:在C++全局作用域中,static修饰的变量/对象是编译单元私有的——也就是说,每个编译单元里的static对象都是独立的,链接器不会对不同编译单元里的同名static对象做合并。

所以当你分步编译时:

  • test.o里有一份属于自己的static A a
  • A.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.cppA.cpp,只编译test.cpp即可:

g++ test.cpp -o linkedTest

这样结果就和方式2完全一致了。

内容的提问来源于stack exchange,提问作者Hack06

火山引擎 最新活动