学过C/C++的亲想必也知道,C类型字符数组(数组存储的字符串)初始化问题在谭浩强(其实我真不想说此人……)那本“万年不烂”的C语言入门书籍上是这样给予说明的
char c[] = "hello world";
或者
char c[] = {'h','e','l','l','o',' ','w','o','r','l','d',NULL};
尽管在对字符数组的初始化问题上,两者所达到的功效是一样的,或者说前者看上去会更友好,所以估计大部分人也就一直是使用前一种初始化方式,而不仅是谭浩强的书,市面上大部分有关C语言的书籍也并没有给出两种方法的实现在不同环境下到底有什么差异,所以想必大部分人也没有其深究这个问题。
然而,在不同平台不同编译器环境上,两种实现方式对字符数组的初始化在过程上还是有很大差异的,缘何如此说?那么现在就给大家从汇编的角度,来看看这两者到底有什么差异。
现在我们初始化两个C字符数组
char c[] = {'h','e','l','l','o',' ','w','o','r','l','d',0};
char a[] = "hello world";
接下来看看反汇编
VS下O2优化
GCC下O2优化(Win下的IDA视图)
(Mac环境下希望有小伙伴可以提供看看)
从反汇编上看,VC++上的差异是明显的。在VS实现字符数组的初始化中,前者(字符数组c)是以"mov 存储单元,对应字符的ascii码"的形式来对每一个数组元素进行赋值,而后者则是直接将某个数据地址以每次4字节的形式先拷贝到通用寄存器中,再通过其中转来赋给字符数组a的每一个元素。而GCC上,两者都在数据段留下了副本,而并没有同VS那样硬编码到代码段。
这个不起眼的初始化问题在应用开发上确实没有什么大的差别,但是在VC++的开发中,这两种不同的赋值方式却是十分值得相关人员注意的,为什么这样说?从前面内容可知,后者会将初始化内容在数据段留下一个“副本”,于是通过这段“副本”,对于喜欢搞“爆破”的人来说,就相当于给他们开了一个捷径,使其迅速找到了软件的"爆破点",对于那些使用内部算法来实现注册但是却未加壳的软件来说,破解真的就是分分秒秒的事情.所以说,后者的赋值你说是不是相当于告诉了那些破解者们"我在哪里干了什么"?
而前者的赋值方式,由于相应的初始值是被硬编码到可执行代码段的,所以一定程度上来说是有一定隐藏作用的。
另感谢@bigtan 指出错误,所以重新修改了本文的观点,由于刚刚入安全这一行,各种观点还欠缺成熟的考虑,还望大家在日后多多指出本人的错误
(PS:本文中的VC++版本均已做了从VC6至现今版本的测试)