【发布时间】:2018-12-18 06:53:38
【问题描述】:
全局变量的一个问题是跨翻译单元的初始化顺序未定义,我们有一些实践来避免全局变量。但我仍然想了解跨翻译单元的全局变量的初始化顺序,仅用于教育目的。
假设我们有这样的代码:
action_type.h
struct ActionType {
static const ActionType addShard; // struct static variables
}
action_type.cpp
ActionType ActionType::addShard(addShardValue);
action_set.h
ActionSet(ActionType s);
我的.cpp:
// global variables
ActionSet s(ActionType::addShard);
我的问题是:
- 我能否始终从全局“s”变量中获取准确的值? s 取决于在不同翻译单元中定义的 ActionType::addShard。
- 如果不能保证,如何编译/链接/运行得到错误的结果?我听说顺序取决于链接阶段。
====为了让话题2更容易讨论,这里是我的测试代码====
// cat action.h
#ifndef ACTION_H
#define ACTION_H
#include <iostream>
#include <bitset>
namespace m {
class ActionSet {
public:
ActionSet();
ActionSet(std::initializer_list<int> ids);
void dump() const;
private:
std::bitset<4> _actions;
};
}
#endif /* ACTION_H */
// action.cpp
#include "action.h"
#include <iostream>
namespace m {
ActionSet::ActionSet(): _actions(0) {
std::cout << "from default" << std::endl;
}
ActionSet::ActionSet(std::initializer_list<int> ids) {
std::cout << "from init list.." << std::endl;
for(auto id : ids) {
_actions.set(id, true);
}
}
void ActionSet::dump() const {
for(int i=0; i<4; i++) {
std::cout << _actions[i] << ",";
}
std::cout << std::endl;
}
}
// const.h
#ifndef CONST_H
#define CONST_H
namespace m {
struct X {
static int x;
static int y;
};
}
#endif /* CONST_H */
// const.cpp
#include "const.h"
namespace m {
int X::x = 0;
int X::y = 2;
};
// f.h
#ifndef F_H
#define F_H
#include "action.h"
#include <iostream>
namespace m {
void f1();
void f2();
}
#endif /* F_H */
// f.cpp
#include "f.h"
#include "const.h"
namespace m {
const ActionSet s{X::x, X::y};
void f1() {
s.dump();
}
void f2() {
const ActionSet s2{X::x, X::y};
s2.dump();
}
};
// action.h
#ifndef ACTION_H
#define ACTION_H
#include <iostream>
#include <bitset>
namespace m {
class ActionSet {
public:
ActionSet();
ActionSet(std::initializer_list<int> ids);
void dump() const;
private:
std::bitset<4> _actions;
};
}
#endif /* ACTION_H */
// action.cpp
#include "action.h"
#include <iostream>
namespace m {
ActionSet::ActionSet(): _actions(0) {
std::cout << "from default" << std::endl;
}
ActionSet::ActionSet(std::initializer_list<int> ids) {
std::cout << "from init list.." << std::endl;
for(auto id : ids) {
_actions.set(id, true);
}
}
void ActionSet::dump() const {
for(int i=0; i<4; i++) {
std::cout << _actions[i] << ",";
}
std::cout << std::endl;
}
}
// main.cpp
#include "f.h"
int main(int argc, char *argv[])
{
m::f1();
m::f2();
return 0;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(project_name)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_EXTENSIONS off)
set(CMAKE_EXPORT_COMPILE_COMMANDS on)
set( CMAKE_VERBOSE_MAKEFILE on )
add_executable(main const.cpp main.cpp f.cpp action.cpp)
add_executable(main2 main.cpp f.cpp action.cpp const.cpp)
【问题讨论】:
-
把目标文件的顺序倒过来,会报错:
clang++ my.o action_type.o -
无法保证得到正确的结果或错误的结果。您可能会发现链接顺序在一个编译器中可靠地工作,但另一个编译器可能无法预测会发生什么。
-
@liliscent 我按照您的建议进行了尝试,看起来它不起作用。我在上面粘贴了我的代码。你能指出有什么问题吗?
-
@zhihuifan 你应该做一个minimal的例子来测试错误。只需两个少于 10 LOC 的文件就足够了。至于您的代码,您使用全局
int进行测试,这是错误的。通常 int 只是写在数据段里面,它们不需要运行时初始化。 -
@zhihuifan "unspecified" 只对 C++ 标准有意义,实际代码最终在机器上运行,所以编译器和系统运行时库应该有办法确定每个细节。例如,mac 上的 clang 将
.o文件的所有初始化条目并列到 mach-o 部分__mod_init_func,然后运行时映像加载器将这些指针一一运行。 gcc linux 有一个类似的.init_array部分(我不熟悉MSVC)。但所有这些都依赖于平台/编译器,我建议你不要编写依赖于这些实现细节的代码。
标签: c++ global-variables static-initialization