C++里面一个被我误解N年的知识点

最早学编程的时候,定义字符串数组往往采用以下写法:

char szBuffer[1024] = {0};

这个定义在早起的VS版本比如说Microsoft Visual Studio 2010 Debug模式下,其反汇编是这样的

1 1     volatile char szBuffer[1024] = { 0 };
2 2 005D95D4    mov         byte ptr [szBuffer],0  
3 3 005D95DB    push        3FFh  
4 4 005D95E0    push        0  
5 5 005D95E2    lea         eax,[ebp-413h]  
6 6 005D95E8    push        eax  
7 7 005D95E9    call        _memset (055BF0Fh)  
8 8 005D95EE    add         esp,0Ch

 这段代码相信即使不怎么懂汇编也能看明白是在做什么,byte代表一个字节,也就是说第一行汇编的含义就是拿到szBuffer的地址,然后取出第一个字节,将这个字节设置为0。然后调用_memset将整个buffer后续3FFh长度(也就是1024-1)全部清0,可能还是VS版本有点老的原因,其_memset函数传参和堆栈平衡的方式和后续都还不一样。当然多年前刚开始学编程肯定理解的没有这么深刻,也忘了是什么原因竟然让我误以为这种写法让整个szBuffer清0是C语言的标准,以至于最后写成了习惯。但是前些年因为工作需要,做了一段时间基于嵌入式Linux和RTOS开发,同样是这种写法,发现也只是修改了第一个字节,一开始我还以为是操作系统的原因,所以仅仅只是做了针对剪裁过的嵌入式Linux和RTOS更改。随后我还是觉得不放心,所以在Release版本上再看了一下,结果没想到是这样的:

1      volatile char szBuffer[1024] = { 0 };
2 008533A0  mov         byte ptr [szBuffer],0  

这里也就是说仅仅只改变了第一个字节,这恰好也和Debug版本第一行代码相互印证。这样真是击溃了我当时认识,因为这种写法是将整个buff清0是我数年的认知,突然有一天告诉我这样是错的。。。。不过也不得不承认,所以那之后也改变了看法。

 

然而事情就这样结束了吗?当然不是。

 

随着VS版本不断升级,我突然发现在Microsoft Visual Studio Community 2019 版本:16.11.10 X64 Debug模式下,这种写法又会自动将szBuffer设置成0,以反汇编的角度来看是这样的:

00007FF6A9DE9324  lea         rax,[szBuffer]  
00007FF6A9DE9328  mov         rdi,rax  
00007FF6A9DE932B  xor         eax,eax  
00007FF6A9DE932D  mov         ecx,400h  
00007FF6A9DE9332  rep stos    byte ptr [rdi]  

以上可以看出,首先取得szBuffer地址,然后将其保存至rdi,然后将eax设置成0,获取到字符串数组长度,这里是400h,也就是十进制的1024。然后调用rep stos指令将整个数组按字节清0。

stos指令就是将eax的值存入rdi指向的内存地址,写入长度为1

rep则是将stos指令重复执行ecx次,也就是400h=1024次。

 

但是如果是同版本Microsoft Visual Studio,Release版本下开启优化之后,是这样的

1     volatile char szBuffer[1024] = {0};
2 00007FF676F62ECB  xor         edx,edx  
3 00007FF676F62ECD  mov         r8d,400h  
4 00007FF676F62ED3  lea         rcx,[szBuffer]  
5 00007FF676F62ED8  call        memset (07FF676F68F30h)

这里也是调用memset将内存区域清0,从这可以看出此时memset调用约定已经和VS2010不一样了。从这次分析可以看出来,在早期VS比如说Microsoft Visual Studio 2010版本,类似于char szBuffer[1024] = {0};这种写法在Release版本只改变第一个字节值为0,而到后来则改成将整个szBuffer清0。至于从哪个版本开始我就不去查证了。不过这么以来就有一个比较严重的问题,字符串大小空间怕是不能随意设置,因为频繁调用memset也会消耗系统资源。

 

此时我突然想起如果在循环中写下类似char szBuffer[1024] = {0};这种代码,那么是不是会不断调用memset?如果在定义好szBuffer之后,再主动调用memset,是不是会重复调用多此一举?

posted @ 2022-03-22 09:24  繁星jemini  阅读(112)  评论(0编辑  收藏  举报