真假常量——const和define

常量,通常指在程序中出现的数字1,2,3,等,字符串Hello World,以及数组名称等,他们都属于常量。在程序中是不允许修改他们的值

虚假常量const挑战真正常量define

下面一段程序:

代码前面定义了:

#define ZS 2234;

真假常量——const和define

程序调试,反汇编如下:

真假常量——const和define

如上图所示:

首先:程序是将立即数8BAh(立即数可以作为汇编的操作数)直接存储到const修饰的常量cNum中,其地址为ebp-0Ch

第一条cout指令输出cNum的地址:ebp-0Ch,同时将其存入ecx并进栈,作为cout函数调用的参数;

第二条cout指令输出cNum的值:

可以看到,程序中并没有使用到cNum变量的地址[ebp-0Ch],而是通过直接将立即数:8BAh(=2234)直接压栈,作为cout函数的参数;

也就是说当程序中需要用到cNum的值得时候,系统已经用“真正的值”对其进行了替换。

综上所述,可以发现,所谓的const修饰的常量,其本质还是变量,程序依然是在堆栈中进行空间分配。那么程序又是通过什么来阻止程序员修改const修饰的变量的呢?《C++反汇编与逆向分析技术揭秘》一书中曾提到,const关键字的作用域是在程序编译过程中,也就是说程序在编译过程中通过某种检测能够阻止对const修饰的变量的修改。可以尝试对cNum变量进行修改,然后对文件进行编译,发现编译过程中就会出现错误。那么到底怎么才能修改cNum内存中的数值呢?现在的限制条件只有const关键字一条,那么我们可以将cNum的地址复制给一个没有const修饰的指针,例如pNum,然后通过pNum来修改内存中的值。尝试如下。

int *pNum=(int*)&cNum;

*pNum=1111;

cout<<(int*)pNum<<endl;

cout<<*pNum<<endl;

cout<<cNum<<endl;

可以发现,输出结果为:

1111

2234

cNum所指向的内存空间中的数值已经修改,但是编译过的程序中出现cNum的地方还是维持原来的数值(2234

真假常量——const和define

(既然在出现cNum的地方,编译器已经用已知的预设值对其进行了替换,那么修改变量内存中的值起始对程序应该是没有任何影响的。)

真正的常量:数字、字符、字符串

程序中的数字、字符都是直接作为立即数参与汇编指令运算。即其存储位置是代码段。结果如下图:(利用VS2008自带的反汇编窗口,选中“显示代码字节”)

真假常量——const和define

可见,十六进制的数:0x12345678直接被编译成了汇编指令,存储在代码段:0x00411554h位置。

也可利用微软Visual Studio提供的命令行编译工具:dumpbin.exe(通常存在的路径是:C:\Program Files\Microsoft Visual Studio 9.0\VC\bin\

dumpbin /all  XXX.exe,结果截图如下:

真假常量——const和define

下面看一下字符串的存储:

(测试代码同上)

真假常量——const和define

如上图所示:

第一条指令:

将“Hello World”传递给str字符串的时候,直接从文件的只读区00417810h读取,从反汇编代码可以看出,程序利用eaxecxedx三个寄存器将00417810h只读区域的字符串常量“Hello World”拷贝到str所代表的内存堆栈空间ebp-38h中。

第二条指令:定义了一个字符串指针,可以看到其汇编指令只有短短的一行,只是将字符串在只读区域的地址00417880h传递给了指针所指向的堆栈空间。

继续查看利用字符串名和字符串指针来输出的代码:

真假常量——const和define

两者指令完全相同,都是将所指向的字符串的地址传递给了I/O流函数cin

同样利用dumpbin.exe工具可以看到只读区域的原始代码如下:

真假常量——const和define

转载于:https://my.oschina.net/zssure/blog/62839