局部静态变量只能初始化一次是怎么实现?
静态变量可以分为全局静态变量,和局部静态变量,先来说说全局的吧。全局静态变量和全局变量的区别并不大,只是全局静态变量只能在当前文件中使用,而在反汇编中二者并无区别,只可以在当前文件中使用,不过是编译器做出的限制。局部静态变量,会有些特殊,它不会随着作用域结束而消失,在未进入作用于之前就已经存在。局部静态变量和全局变量都保存在二进制文件的数据区,而在代码中的限制,不过是编译器限制而已。
来看代码:
void ShowStatic(int nNum) { static int gnNumber = nNum; printf("%d\n", gnNumber); } void main() { ShowStatic(99); }
汇编代码:
00E51738 mov eax,dword ptr ds:[00E5A148h] 00E5173D and eax,1 00E51740 jne ShowStatic+47h (0E51757h) 00E51742 mov eax,dword ptr ds:[00E5A148h] 00E51747 or eax,1 00E5174A mov dword ptr ds:[00E5A148h],eax 00E5174F mov eax,dword ptr [nNum] 00E51752 mov dword ptr [gnNumber (0E5A144h)],eax
可以看出,静态变量的赋值比普通变量赋值多了很多步骤,我们来分析下。
首先在地址00E5A148h中保存了局部静态变量的标志,这个标志占1个字节。通过位运算,将标志中的一位数据置1,来判断局部静态变量是否初始化过。而这个标志可以同时保存8个局部静态变量的初始状态。
通常这个标志出现在最先定义的局部静态变量的附近,例如此例局部变量应出现在 00E5A144h 或 00E5A14Ch中。当同一个作用域内超过了8个静态局部变量,下一个标记将会除了现在第9个定义的局部静态变量地址的附近。现在再来看上面的汇编代码就很清晰了:
00E51738 mov eax,dword ptr ds:[00E5A148h] 00E5173D and eax,1 00E51740 jne ShowStatic+47h (0E51757h)
断是否已经初始化,如果已经初始化就跳转到printf输出内容,否则不跳转继续执行。
00E51742 mov eax,dword ptr ds:[00E5A148h] 00E51747 or eax,1 00E5174A mov dword ptr ds:[00E5A148h],eax 00E5174F mov eax,dword ptr [nNum] 00E51752 mov dword ptr [gnNumber (0E5A144h)],eax
未初始化的情况,将标志位置位为1,并初始化gnNumber。
还有这样一个问题,编译器让其他作用域对局部静态变量不可见,这是怎么做到的?在编译的过程中,编译器会对变量,函数等进行名称粉碎,也就是静态变量被重新命名了。
我们观察下编译期结束后生成的obj文件,在这个文件中搜索静态变量的名字(本文用HxD软件打开obj文件),搜索结果如下图:
名称粉碎后,在原有名称中加加入了一些额外信息,入作用域,类型等。像C++重载也是名称粉碎的原理。
static int gnNumber = nNum; 00C11818 mov eax,dword ptr [_tls_index (0C1B190h)] 00C1181D mov ecx,dword ptr fs:[2Ch] 00C11824 mov edx,dword ptr [ecx+eax*4] 00C11827 mov eax,dword ptr ds:[00C1B150h] 00C1182C cmp eax,dword ptr [edx+104h] 00C11832 jle ShowStatic+6Fh (0C1185Fh) 00C11834 push 0C1B150h 00C11839 call __Init_thread_header (0C110DCh) 00C1183E add esp,4 00C11841 cmp dword ptr ds:[0C1B150h],0FFFFFFFFh 00C11848 jne ShowStatic+6Fh (0C1185Fh) 00C1184A mov eax,dword ptr [nNum] 00C1184D mov dword ptr [gnNumber (0C1B14Ch)],eax 00C11852 push 0C1B150h 00C11857 call __Init_thread_footer (0C11177h) 00C1185C add esp,4
前三行代码:
00C11818 mov eax,dword ptr [_tls_index (0C1B190h)] 00C1181D mov ecx,dword ptr fs:[2Ch] 00C11824 mov edx,dword ptr [ecx+eax*4]
TLS?怎么还多了两个函数?__Init_thread_header
和_Init_thread_footer。
这两个函数是用来保证局部的静态对象的初始化线程安全。但局部变量的互斥还是老样子,只不过被封装进上述的两个函数之中了。