首页
社区
课程
招聘
[原创]局部静态变量只能初始化一次?它是怎么实现的
2019-10-17 21:19 4387

[原创]局部静态变量只能初始化一次?它是怎么实现的

2019-10-17 21:19
4387

静态变量可以分为全局静态变量,和局部静态变量,先来说说全局的吧

 

全局静态变量和全局变量的区别并不大,只是全局静态变量只能在当前文件中使用,而在反汇编中二者并无区别,只可以在当前文件中使用,不过是编译器做出的限制。

 

局部静态变量,会有些特殊,它不会随着作用域结束而消失,在未进入作用于之前就已经存在。

 

局部静态变量和全局变量都保存在二进制文件的数据区,而在代码中的限制,不过是编译器限制而已。

 

那么当某个函数频繁调用局部静态变量时,C++的语法规定局部静态变量只能初始化一次,那么编译器是怎么做到的呢。

 

来看代码:

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++重载也是名称粉碎的原理。

 

下面的汇编是在C++11中编译的结果,显然和上文的有些差距:

    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

 

这两个函数是用来保证局部的静态对象的初始化线程安全。

 

但局部变量的互斥还是老样子,只不过被封装进上述的两个函数之中了。

 

有兴趣的读者可以自己上机调试一番。

 

文章重点:
1,在编译的过程中,编译器会对变量,函数等进行名称粉碎,也就是静态变量被重新命名了
2,有个1字节标志判断最多八个静态变量是否初始化 这个标志附近存放着对应的静态变量

 


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2019-10-20 01:13 被Hasic编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (4)
雪    币: 709
活跃值: (494)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
666666 2019-10-18 08:46
2
0
雪    币: 83
活跃值: (1037)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
killpy 2 2019-10-19 16:04
3
0
文章重点:
1,在编译的过程中,编译器会对变量,函数等进行名称粉碎,也就是静态变量被重新命名了
2,有个1字节标志判断最多八个静态变量是否初始化 这个标志附近存放着对应的静态变量
雪    币: 612
活跃值: (479)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Hasic 2019-10-20 00:46
4
0
killpy 文章重点: 1,在编译的过程中,编译器会对变量,函数等进行名称粉碎,也就是静态变量被重新命名了 2,有个1字节标志判断最多八个静态变量是否初始化 这个标志附近存放着对应的静态变量
谢谢大佬指点
雪    币: 6
活跃值: (2895)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
咖啡_741298 2019-12-18 01:10
5
0
Hasic 谢谢大佬指点
没懂怎么防止多线程初始化的,还加了互斥锁? mutex
游客
登录 | 注册 方可回帖
返回