赞
踩
原文http://www.endlessbright.org/?p=51
这是之前公司简称我司的一位已经离职同事赵工曾写文章,此文章在上面做一定扩展,因为是公司内部论坛,因此无法贴出原文链接。
有一段有趣的代码:
#include <stdio.h>
int func_a();
int func_b();
int func_c();
int func_a() {
static int a = func_b();
return a;
}
int func_b() {
static int b = func_c();
return b;
}
int func_c() {
static int c = func_a();
return c;
}
int main()
{
printf("%d\n", func_a());
return 0;
}
上面程序在Linux和windows下面输出的结果会是什么?
Windows下面输出为0,而在Linux下面则编译失败。
在Linux环境下面用gcc编译直接失败,部分错误信息:
/tmp/ccnpmELe.o:In function `func_a()':
main.cpp:(.text+0x19):undefined reference to `__cxa_guard_acquire'
main.cpp:(.text+0x3d):undefined reference to `__cxa_guard_release'
main.cpp:(.text+0x5a): undefined reference to`__cxa_guard_abort'
g++则是在运行时coredump,gdb查看汇编可以看到锁(汇编知识缺乏,后面学习了再补详细说明):
0x000000000040074b<+7>: mov $0x601058,%eax
0x0000000000400750 <+12>: movzbl (%rax),%eax
0x0000000000400753 <+15>: test %al,%al
0x0000000000400755 <+17>: jne 0x400785 <_Z6func_av+65>
0x0000000000400757 <+19>: mov $0x601058,%edi
0x000000000040075c <+24>: callq 0x400600 <__cxa_guard_acquire@plt>
0x0000000000400761 <+29>: test %eax,%eax
0x0000000000400763 <+31>: setne %al
0x0000000000400766 <+34>: test %al,%al
0x0000000000400768 <+36>: je 0x400785 <_Z6func_av+65>
0x000000000040076a <+38>: mov $0x0,%r12d
0x0000000000400770 <+44>: callq 0x4007ad <_Z6func_bv>
=>0x0000000000400775 <+49>: mov %eax,0x2008fd(%rip) # 0x601078 <_ZZ6func_avE1a>
0x000000000040077b <+55>: mov $0x601058,%edi
0x0000000000400780 <+60>: callq 0x400620 <__cxa_guard_release@plt>
0x0000000000400785<+65>: mov 0x2008ed(%rip),%eax # 0x601078 <_ZZ6func_avE1a>
在windows下面可以运行,返回0。在静态变量后面有一个int(地址378154h)保存着此静态变量是否初使化标志,根据这个标示是否为1判断是否已经初使化。
static int a = func_b();
0037140D mov eax,dword ptr [$S1 (378154h)]
00371412 and eax,1
00371415 jne func_a+6Ch (37143Ch)
00371417 mov eax,dword ptr [$S1 (378154h)]
0037141C or eax,1
0037141F mov dword ptr [$S1 (378154h)],eax
00371424 mov dword ptr [ebp-4],0
0037142B call func_b (3710F0h)
00371430 mov dword ptr [a (378144h)],eax
00371435 mov dword ptr [ebp-4],0FFFFFFFFh
return a;
0037143C mov eax,dword ptr [a (378144h)]
Linux加入锁从而保证在多线程情况下此静态变量一定被初使化,而在windows多线程临界情况可能出现此静态变量未初使化情况。
Windows存在未初使化问题,当类中存在动态内存分配时就会出现未定义行为。但此类函数本身就不保证线程安全,可以理解。
Gcc使用锁,严格保证使用时被初使化,当存在这种循环锁还能够编译错误。G++则相对的弱一点。
但并不是所有的函数static变量都有初使化过程,例如:
int func_test()
{
static int a = 0;
a++;
Return a;
}
这捉情况下并没有a初使化编译代码,其值在.data段中。程序运行时只需要直接映射到内存即可。
理论上只要此静态变量类型中的所有数据都可以在编译时期确定的话(一般是该类型不需要动态分配堆上面的内存),就没有必要在运行期在去做初使化过程。
例如:
struct Test_A
{
int a;
int b;
Test_A()
{
a = 0;
b = 1;
}
};
void func_a()
{
static Test_A a;
}
这种情况下a应该可以不用再做初使化,直接将数据写入到.data即可,从windows测试程序来还是进行了初使化过程。
这只能算是一个优化需求,不清楚开启优化选项是否能优化掉。
静态变量地址在编译链接时期就已经确定,其地址上面内容则是根据其初使化而定,一般基本数据类型都不需要运行期再初使化,而自定义类型则需要(可以去思考哪些情况其实是可以优化成不需要运行期去实现的)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。