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,是不是会重复调用多此一举?