无符号整数的取值范围:0x00000000 ~ 0xFFFFFFFF。
有符号整数的最高位为符号位,取值范围:0x80000000 ~ 0x7FFFFFFF
转换成二进制,分别为:
0x80000000 = 1000 0000 0000 0000 0000 0000 0000 0000
0x7FFFFFFF = 0111 1111 1111 1111 1111 1111 1111 1111
正数的表示区间为:0x00000000 ~ 0x7FFFFFFF
负数的表示区间为:0x80000000 ~ 0xFFFFFFFF
负数在内存中都是以补码的形式存放的,补码的规则是用0减去这个数的绝对值,也可以表示为,对这个数取反加1。例如,对于-3,可以表达为0-3,而 0xFFFFFFFD + 3 等于0 (进位丢失),所以-3的补码也就是0xFFFFFFFD了。
为了计算方便,通常采用取反加1的方式来获得补码,因为对于任何4字节的数值,都有x + x(反)= 0xFFFFFFFF,于是 x + x(反) + 1 = 0
对于4字节的补码,0x80000000所表达的意义,可以是负数0
,也可以是0x80000001 减 1
,由于0的正负值是相等的,因此没有必要还来个负数0,因此将这个值的意义规定为:0x8000000 - 1
,这样,0x80000000也就成了4字节负数的最小值,负数区间总是比正数区间多一个最小值的原因了。
在数据分析中,如果将内存解释为有符号整数,则查看16进制表示的最高位,最高位小于8则为正数,大于8则为负数。如果是负数,则需要转换成真值,从而得到对应的负数数值。
问:如何判断一段数据是有符号类型还是无符号类型呢?
答:需要查看指令或者已知的函数如何操作此内存地址,根据操作方式或函数相关定义得出该地址的数据类型。 如,MessageBoxA,它有4个参数,查看帮助得知,第四个参数是一个无符号整数。
只所以叫做浮点数
,是因为小数点可以浮动,因此,叫做浮点数。
浮点数是采用IEEE规定的标准编码,float和double这两种类型的转换原理相同。
float类型在内存占4个字节(32位)。 最高位用于表示符号,在剩余的32一位中,从右往左取8位用于表示指数,其余用于表示尾数。
符号位 指数位 尾数位
0 00000000 00000000000000000000000
double类型在内存中占8个字节。最高位表示符号,指数位占11位,剩余的42位用于表示尾数。
关于浮点数到二进制的转换,参考:http://www.0x520.com/2007/10/16/171.html
下面演示,如何将float类型12.25f转换为IEEE编码:
1、将12.25f转换成二进制数1100.01,整数部分为1100,小数部分为01。
2、小数点向左移动,每移动一次,指数加一。移动到符号位的最高位为1处,停止移动,这里仅移动3次。
3、在IEEE编码中,在二进制情况下,尾数的最高位始终为1,为一个恒定值,故将其忽略不计。
12.25f经IEEE转换后的情况:
符号位:0
指数位:十进制的3+127 ,转换成二进制 10000010
尾数为:10001 000000000000000000(不足23位时,低位补0填充)
4、由于尾数位中最高位始终为1,是恒定值,故省略不计,只要在转换为十进制时加1即可。
5、为什么指数为要加127呢? 由于指数可能出现负数,十进制数127可表示为二进制 01111111 IEEE规定,当指数小于 01111111时为一个负数,反之为正数,因此 01111111为0 。
6、12.25f转换后二进制表示为0 10000010 10001000000000000000000
,转换成16进制为 0x41440000
下面演示,如何将float类型-0.125转换为IEEE编码:
1、根据科学记数法,小数点向整数部分移动,指数做加法;向小数部分移动,指数做减法。
2、-0.125转换为二进制位 0.001,用科学记数法表示为:1.0 指数位 -3
-0.125转换后的情况
符号位:0
指数位:十进制 127 + (-3),转换为二进制:01111100,如果不足8位,则高位补0
尾数位:00000000000000000000000(23个0)
如果尾数部分转换为二进制后是一个无穷值,则会舍弃部分位数,所以进行IEEE转换后得到的是一个近似值,存在一定的误差,这就解释了为什么C++在比较浮点数是否为0时,要在一个区间比较,而不是直接等于0比较,如下:
float fTemp = 0.0001f; //精确范围
if(fFloat >= -fTemp && fFloat <= fTemp)
{
//fFloat 等于0
}
int main(int argc, char* argv[])
{
float fFloat = (float) argc;
printf("%f",fFloat);
argc = (int) fFloat;
printf("%d",argc);
return 0;
}
我将这段代码进行了反编译
10: float fFloat = (float) argc;
//将地址 ebp+8 处的 argc 这个整型转换为浮点型,并放入ST(0)中
00410648 DB 45 08 fild dword ptr [ebp+8]
//从ST(0)中取出数据,以浮点编码方式放入地址ebp-4中,对应变量fFloat
0041064B D9 55 FC fst dword ptr [ebp-4]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11: printf("%f",fFloat);
//这里对esp-8操作,是由于浮点数作为`变参函数`的参数时,需要转换为double类型
//这步操作提前准备8个字节的栈空间,以便于存放double数据
0041064E 83 EC 08 sub esp,8
//将ST(0)中的数据传入esp,并弹出ST(0)
00410651 DD 1C 24 fstp qword ptr [esp]
//以下为printf函数调用
00410654 68 2C 60 42 00 push offset string "%f" (0042602c)
00410659 E8 B2 02 00 00 call printf (00410910)
0041065E 83 C4 0C add esp,0Ch
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
12: argc = (int) fFloat;
//将地址ebp-4处的数据,以浮点型压入 ST(0) 中
00410661 D9 45 FC fld dword ptr [ebp-4]
//调用__ftol将浮点数转换为int,转换后的结果会放在eax中
00410664 E8 3B 02 00 00 call __ftol (004108a4)
//将转换后的结果,放入到 ebp+8 地址处
00410669 89 45 08 mov dword ptr [ebp+8],eax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13: printf("%d",argc);
0041066C 8B 45 08 mov eax,dword ptr [ebp+8]
0041066F 50 push eax
00410670 68 1C 60 42 00 push offset string "%d" (0042601c)
00410675 E8 96 02 00 00 call printf (00410910)
0041067A 83 C4 08 add esp,8
14: return 0;
0041067D 33 C0 xor eax,eax
在C++中字符的编码分两种方式:ASCII和Unicode。
关于Unicode的详细介绍参考我这篇文章:http://www.0x520.com/2014/06/25/152.html
在C++中使用结束符 '\0'
作为字符串的结束标识。ASCII使用一个字节 '\0'
,Unicode使用两个字节 '\0'
。
C++中定义 0 为假,非 0 为真。
在C++中,地址标号使用16进制表示。取一个变量的地址使用 & 符号,只有变量才存在内存地址,常量没有地址。
指针的定义使用 TYPE*,TYPE为数据类型。任何数据类型都可以定义为指针。指针本身也是一种数据类型,它用于保存各种数据类型在内存中的地址。指针变量同样可以取地址。
引用的定义使用 TYPE&,TYPE为数据类型。在C++中是不可单独定义的,并且在定义时就要初始化。引用表示一个变量的别名,对它的任何操作,本质上都是在操作它所表示的变量。
指针也是变量,也包含在内存中,所以可以取出指针变量在内存中的位置–地址。指针变量在内存中占4个字节的内存空间。
指针可以根据指针类型
对地址对应的数据进行解析。由于每种数据类型在内存中占用的空间不同,指针只保存了存放数据的首地址,而没有指明在哪里结束。这时,就需要根据对应的类型,来寻找解释数据的结束地址。
我写了一段测试代码
int main(int argc, char* argv[])
{
int nVar = 0x12345678;
int *pnVar = &nVar;
char *pcVar = (char*)&nVar;
short *psVar = (short*)&nVar;
printf("%08x \r\n", *pnVar);
printf("%08x \r\n", *pcVar);
printf("%08x \r\n", *psVar);
return 0;
}
反汇编代码如下:
10: int nVar = 0x12345678;
//为地址 ebp-4 赋值4字节数据 12345678h
0040D778 C7 45 FC 78 56 34 12 mov dword ptr [ebp-4],12345678h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
//定义一个int类型的指针
11: int *pnVar = &nVar;
//取ebp-4处的地址,并放在eax中
0040D77F 8D 45 FC lea eax,[ebp-4]
//并将eax中的地址值放入到ebp-8处
0040D782 89 45 F8 mov dword ptr [ebp-8],eax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
//定义一个char类型的指针
12: char *pcVar = (char*)&nVar;
0040D785 8D 4D FC lea ecx,[ebp-4]
0040D788 89 4D F4 mov dword ptr [ebp-0Ch],ecx
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
//定义一个short类型的指针
13: short *psVar = (short*)&nVar;
0040D78B 8D 55 FC lea edx,[ebp-4]
0040D78E 89 55 F0 mov dword ptr [ebp-10h],edx
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14: printf("%08x \r\n", *pnVar);
//取出pnVar中保存的地址,放入到eax中
0040D791 8B 45 F8 mov eax,dword ptr [ebp-8]
//以4字节方式读取数据
0040D794 8B 08 mov ecx,dword ptr [eax]
0040D796 51 push ecx
0040D797 68 80 2E 42 00 push offset string "%08x \r\n" (00422e80)
0040D79C E8 3F FF FF FF call printf (0040d6e0)
0040D7A1 83 C4 08 add esp,8
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15: printf("%08x \r\n", *pcVar);
0040D7A4 8B 55 F4 mov edx,dword ptr [ebp-0Ch]
//以1字节方式读取数据
0040D7A7 0F BE 02 movsx eax,byte ptr [edx]
0040D7AA 50 push eax
0040D7AB 68 80 2E 42 00 push offset string "%08x \r\n" (00422e80)
0040D7B0 E8 2B FF FF FF call printf (0040d6e0)
0040D7B5 83 C4 08 add esp,8
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
16: printf("%08x \r\n", *psVar);
0040D7B8 8B 4D F0 mov ecx,dword ptr [ebp-10h]
//以2字节方式读取数据
0040D7BB 0F BF 11 movsx edx,word ptr [ecx]
0040D7BE 52 push edx
0040D7BF 68 80 2E 42 00 push offset string "%08x \r\n" (00422e80)
0040D7C4 E8 17 FF FF FF call printf (0040d6e0)
0040D7C9 83 C4 08 add esp,8
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
17: return 0;
0040D7CC 33 C0 xor eax,eax
程序输出的结果:
12345678
00000078
00005678
变量nVar在内存中的数据为 “78 56 23 12” ,首地址从78开始。 所以才得到上面这种结果。
引用在c++中被描述为变量的别名,C++为了简化指针的操作,对指针进行了封装,产生了引用。实际上引用就是指针,只不过它用于存放地址的内存空间对使用者而言是隐藏的。
下面是我写的一段程序:
int main(int argc, char* argv[])
{
int nVar = 0x12345678;
int &nVarType = nVar;
Add(nVar);
return 0;
}
反汇编代码:
14: int nVar = 0x12345678;
00401078 C7 45 FC 78 56 34 12 mov dword ptr [ebp-4],12345678h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15: int &nVarType = nVar;
//取出nVar的地址放入eax中
0040107F 8D 45 FC lea eax,[ebp-4]
//将eax中存放的地址值,放入到ebp-8处,这个ebp-8便是引用类型nVarType的地址
//也就是说明,引用类型在内存中是占有位置的。
00401082 89 45 F8 mov dword ptr [ebp-8],eax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
16: Add(nVar);
00401085 8D 4D FC lea ecx,[ebp-4]
00401088 51 push ecx
00401089 E8 7C FF FF FF call @ILT+5(Add) (0040100a)
0040108E 83 C4 04 add esp,4
17: return 0;
00401091 33 C0 xor eax,eax
下面是Add函数的源码:
void Add(int &nVar)
{
nVar ++;
}
下面是Add函数的反汇编代码:
7: void Add(int &nVar)
8: {
9: nVar ++;
00401038 8B 45 08 mov eax,dword ptr [ebp+8]
0040103B 8B 08 mov ecx,dword ptr [eax]
0040103D 83 C1 01 add ecx,1
00401040 8B 55 08 mov edx,dword ptr [ebp+8]
00401043 89 0A mov dword ptr [edx],ecx
10: }
常量数据在程序运行前就已经存在,它被编译到可执行文件中。程序运行后,他们会被加载进来。这些数据通常都会在常量数据区中保存,该section的属性是没有写权限的,所以对常量修改时,程序会报错。
常量数据的地址减去基址,便是它在文件中的偏移地址。
如果,常量字符串的首地址为 0x00423FA8 ,该程序的基址为 0x00400000,所以对应的偏移地址为:字符串首地址 - 基地址 = 0x00023FA8 ,使用十六进制查看器打开可执行文件,到 0x00023FA8 处,可以看到该字符串。
#define NUMBER_ONE 1
const int nVar = NUMBER_ONE;
#define 是一个真常量,而const却是由编译器判断实现的变量,是一个假常量。 在实际应用中,使用const定义的常量,最终还是一个变量,只是在编译器内进行了检查,发现有修改则报错。
修改const常量:
const int nConst = 5;
int *pConst = (int*)&nConst;
*pConst = 6;
int nVar = nConst;
转载请注明出处:鸡哥的博客http://www.0x520.com/2014/07/01/175.html