从反汇编角度学C/C++系列目录:
一.有符号数和无符号数
我们知道在C/C++中,整数类型分为有符号数和无符号数。在数据宽度相同的时候,他们所能表达的范围是不同的。这是因为对于有符号数来说,他使用最高位来表示数字的正负,当最高位为1时是负数,否则为正数。对于int类型数据来说0x00000000到0x7FFFFFFF用来表示整数,而0x80000000到0xFFFFFFFF用来表示负数。
而在内存中的形式究竟如何?考虑如下的代码:
unsigned int x = -1;
int y = -1;
按照定义我们可以知道y被赋值为-1,而x作为无符号数应该是不可以被赋值为-1。
但是经过反汇编以后我们却得到了如下得汇编代码:
unsigned int x = -1;
009810E8 mov dword ptr [ebp-8],0FFFFFFFFh //将x赋值为-1
int y = -1;
009810EF mov dword ptr [ebp-14h],0FFFFFFFFh //将y赋值为-1
可以发现从汇编层面观察,无论是有符号数还是无符号数在内存中的存储数据都是一样的,都是0xFFFFFFFF。这是因为在计算机中,是没有负数表示,所有的负数都是使用补码形式保存,-1的补码是0xFFFFFFFF。
由此可见,在内存存储的数据中,无论是有符号整数还是无符号整数,结果都是一样的。
而对他们的使用我们考虑如下的反汇编代码:
printf("x=%d\n", x);
008F10F6 mov eax,dword ptr [ebp-8] //将x的值0xFFFFFFFF取出
008F10F9 push eax
008F10FA push 9631B0h //字符串"x=%d\n"的地址
008F10FF call 008F1130 //printf函数地址
008F1104 add esp,8
printf("y=%u\n", y);
008F1107 mov eax,dword ptr [ebp-14h] //将y的值0xFFFFFFFF取出
008F110A push eax
008F110B push 9631B8h //字符串"y=%u\n"的地址
008F1110 call 008F1130 //printf函数的地址
008F1115 add esp,8
可以看到,无论是有符号数还是无符号数,对于他们的使用是完全一样的。这里我将无符号数x用有符号数%d的方式输出,而将有符号数y用无符号数%x的方式输出。最后得到了如下的结果:
无符号数的x输出了-1,有符号数的y却输出了最大的正数。由此可见,无论是有符号数还是无符号数他们在内存中的存储是完全一样的,至于表现形式如何完全取决于程序员如何使用他们。
二.char类型和int类型
我们知道在C/C++中,有字符类型char和整型int。二者的区别是数据宽度的不同,其中char占1个字节,而int占4个字节以及char被我们用来存储字符,而int被我们用来存储数据。那么他们在内存中的表现形式是如何的?考虑如下代码:
char x = 97;
int y = 'a';
我们将字符类型x赋值为整数97,而把整型y赋值为字符类型'a'。最终在内存中的反汇编结果如下:
char x = 97;
003510E8 mov byte ptr [ebp-5],61h //将0x61赋值给x
int y = 'a';
003510EC mov dword ptr [ebp-14h],61h //将0x61赋值给y
可以看到在内存中,最终的形式都是将0x61(十进制为97,对应字符'a')的数字赋值到相应的位置。char和int在赋值时候的唯一区别只是数据宽度不同,char赋值时候为byte,而int为dword。
我们在将字符型的x以整型%d输出,而将整型y以字符型%c输出可以得到如下的汇编代码与结果:
printf("char x = %d\n", x);
001910F3 movsx eax,byte ptr [ebp-5] //将0x61从x中取出放入eax
001910F7 push eax
001910F8 push 2031B0h //字符串"char x = %d\n"的地址
001910FD call 00191130 //printf函数的地址
00191102 add esp,8
printf("int y = %c\n", y);
00191105 mov eax,dword ptr [ebp-14h] //将0x61从y中取出放入eax
00191108 push eax
00191109 push 2031C0h //字符串"int y = %c\n"的地址
0019110E call 00191130 //printf函数的地址
00191113 add esp,8
可以看到对于他们的使用,从汇编层面看除了数据宽度的不同以外其他完全一样。而最终输出结果如下所示:
作为字符串的x输出了97,而作为整型的y输出了字符'a',由此我们可以得出结果字符型与整型的在内存中的存储与使用是完全一样的只是数据宽度不同而已。
三.布尔类型
我们知道布尔类型分为true与false。分别代表了真与假,那么在内存的表现形式是如何的呢?请看以下的实例:
bool x = true;
00031038 mov byte ptr [ebp-5],1 //将x赋值为true
x = false;
0003103C mov byte ptr [ebp-5],0 //将x赋值为false
x = 10;
00031040 mov byte ptr [ebp-5],1 //将x赋值为10
x = -1;
00031044 mov byte ptr [ebp-5],1 //将x赋值为-1
x = 0;
00031048 mov byte ptr [ebp-5],0 //将x赋值为0
我们可以看到,对于布尔类型,0对应false,1对于true。并且,不为0的数无论是正负赋值给布尔类型,最终都会转换为1。
四.常量
在C/C++中使用const修饰的变量我们称之为常量。对于常量如果我们对他进行修改,编译器就会报错。那么常量和普遍变量在内存中有什么样的区别呢。我们看下面这个实例。
const int x = 1900;
00621038 mov dword ptr [ebp-8],76Ch //将1900对用的十六进制数0x76赋值给x
int y = 1900;
0062103F mov dword ptr [ebp-14h],76Ch //将1900对用的十六进制数0x76赋值给y
由上我们可以看出其实在内存中无论是否是用const修饰的变量,他们的表现形式是一样的。那么,我们完全有理由相信,对于const常量只是在编译阶段中的词法分析阶段对相应的token进行了设置。所有我们完全可以使用有办法对他进行修改,如下:
int main()
{
const int x = 1900;
int y = 1874;
int *z = &y;
z++; //指向x的地址
*z = 1234;
printf("%d", *(int *)&x); //输出x的值
return 0;
}
我们将z指向y,同时根据x和y地址的距离将z + 1以后指向x。然后在对z这个地址的数据进行修改。最终实现了对x的修改。输出结果如下:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-2-27 20:34
被1900编辑
,原因: