我有这样的代码:
struct Storage
{
static int GetData()
{
static int global_value;
return global_value++;
}
};
int free_func()
{
static int local_value;
return local_value++;
}
int storage_test_func()
{
return Storage::GetData();
}
在OSX上编译:
$ clang++ 1.cpp -shared
运行nm:
$ nm | c++filt
我得到了奇怪的结果:
0000000000000f50 T storage_test_func()
0000000000000f30 T free_func()
0000000000000f60 unsigned short Storage::GetData()
0000000000001024 bool free_func()::local_value
0000000000001020 D Storage::GetData()::global_value
U dyld_stub_binder
两个符号(local_value
和global_value
)具有不同的链接!一个明显的区别是global_value
在静态成员函数中定义,local_value
在自由函数中定义。
有人可以解释为什么会这样吗?
UPD:
在评论之后看起来我应该澄清一些事情。
也许使用c++filt
是一个坏主意。没有它,它显示:
$ nm
0000000000000f50 T __Z17storage_test_funcv
0000000000000f30 T __Z9free_funcv
0000000000000f60 t __ZN7Storage7GetDataEv
0000000000001024 b __ZZ9free_funcvE11local_value
0000000000001020 D __ZZN7Storage7GetDataEvE5value
U dyld_stub_binder
是的。 __ZZ9free_funcvE11local_value
转到BSS,__ZZN7Storage7GetDataEvE5value
转到数据部分。
man nm
说:
如果符号是本地符号(非外部符号),则符号的类型由相应的小写字母表示。
而我所看到的。 __ZZ9free_funcvE11local_value
标有小写b
,__ZZN7Storage7GetDataEvE5value
标有大写D
。
这是问题的主要部分。为什么会这样?
UPD2 还有一种方法:
$ clang++ -c -emit-llvm 1.cpp
$ llvm-dis 1.bc
显示了这些变量在内部的表示方式:
@_ZZ9free_funcvE11local_value = internal global i32 0, align 4
@_ZZN7Storage7GetDataEvE5value = global i32 0, align 4
UPD3
还有一些关于属于不同部分符号的担忧。将__ZZ9free_funcvE11local_value
放到文本部分不会改变其可见性:
struct Storage
{
static int GetData()
{
static int value;
return value++;
}
};
int free_func()
{
static int local_value = 123;
return local_value++;
}
int storage_test_func()
{
return Storage::GetData();
}
编译:
$ clang++ 1.cpp -shared
检查:
$ nm
给出:
0000000000000f50 T __Z17storage_test_funcv
0000000000000f30 T __Z9free_funcv
0000000000000f60 t __ZN7Storage7GetDataEv
0000000000001020 d __ZZ9free_funcvE11local_value
0000000000001024 D __ZZN7Storage7GetDataEvE5value
现在两个符号都在数据部分,但仍然其中一个是本地,另一个是全局。 问题是为什么它会发生?有人可以根据这样的编译器决定逻辑吗?
答案 0 :(得分:5)
静态成员函数中的局部静态变量与自由函数中的局部静态变量没有区别。
两个符号(local_value和global_value)具有不同的链接!
在标准命名法中,从标准的角度来看,两个变量都有 no 的联系。
函数之间的相关差异不是一个是静态成员函数而另一个不是。相关的区别是前者是内联函数,后者不是。
非内联函数只能在一个翻译单元中定义,因此不需要从其他翻译单元访问其本地静态变量。
另一方面,必须在使用它们的每个翻译单元中定义内联函数。并且,由于本地静态必须全局引用同一个对象,因此该对象必须对多个转换单元可见。
答案 1 :(得分:1)
这些值将链接到不同的存储部分。
000000000001020 D Storage :: GetData():: global_value
D
表示您的变量将链接到将要初始化的部分。从nm
手册页:
"D"
"d" The symbol is in the initialized data section.
local_value
不会通过c-startup代码初始化。
链接后我得到了:
0804a0d0 b free_func():: local_value
0804a0d8 u Storage :: GetData():: global_value
同样来自nm
手册页:
"B"
"b" The symbol is in the uninitialized data section (known as BSS).
"U" The symbol is undefined.
"u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the
dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.
原因很简单,这些值将通过标准启动初始化(数据部分)或不是(bss部分)初始化。该部分的名称通常未指定,但在常见实现中使用。
你可以找到很多Q& A到“var何时以及在哪里初始化var”。 例如。: When are static and global variables initialized?
澄清:(我希望)
变量未通过启动代码初始化并不意味着未初始化。方法/函数内的静态变量通常在包含代码块的第一次访问中初始化。如果您想知道编译器如何完成这项工作,请查看汇编输出。
对于gcc,你会在值初始化周围找到一些__cxa_
标签,以保护你的vars不受多个init的影响。