博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[逆向基础] C++中基本数据类型的表现形式
阅读量:6939 次
发布时间:2019-06-27

本文共 9314 字,大约阅读时间需要 31 分钟。

hot3.png

##一、整数类型 ###1、无符号整数 无符号整数的取值范围:0x00000000 ~ 0xFFFFFFFF。

###2、有符号整数 有符号整数的最高位为符号位,取值范围:0x80000000 ~ 0x7FFFFFFF

转换成二进制,分别为:

0x80000000 = 1000 0000 0000 0000 0000 0000 0000 00000x7FFFFFFF = 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个参数,查看帮助得知,第四个参数是一个无符号整数。

##二、浮点数类型 ###1、浮点数类型的编码方式(Float) 只所以叫做浮点数,是因为小数点可以浮动,因此,叫做浮点数。 浮点数是采用IEEE规定的标准编码,float和double这两种类型的转换原理相同。

float类型在内存占4个字节(32位)。 最高位用于表示符号,在剩余的32一位中,从右往左取8位用于表示指数,其余用于表示尾数。

符号位     指数位        尾数位    0         00000000     00000000000000000000000

double类型在内存中占8个字节。最高位表示符号,指数位占11位,剩余的42位用于表示尾数。

关于浮点数到二进制的转换,参考:

下面演示,如何将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 }

###2、基本的浮点数指令

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中,对应变量fFloat0041064B 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        eax00410670 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,814:       return 0;0041067D 33 C0                xor         eax,eax

##三、字符和字符串

###1、字符的编码 在C++中字符的编码分两种方式:ASCII和Unicode。 关于Unicode的详细介绍参考我这篇文章:

###2、字符串的存储方式 在C++中使用结束符 '\0' 作为字符串的结束标识。ASCII使用一个字节 '\0',Unicode使用两个字节 '\0'

##四、布尔类型 C++中定义 0 为假,非 0 为真。

##五、地址、指针和引用 在C++中,地址标号使用16进制表示。取一个变量的地址使用 & 符号,只有变量才存在内存地址,常量没有地址。

指针的定义使用 TYPE*,TYPE为数据类型。任何数据类型都可以定义为指针。指针本身也是一种数据类型,它用于保存各种数据类型在内存中的地址。指针变量同样可以取地址。

引用的定义使用 TYPE&,TYPE为数据类型。在C++中是不可单独定义的,并且在定义时就要初始化。引用表示一个变量的别名,对它的任何操作,本质上都是在操作它所表示的变量。

指针也是变量,也包含在内存中,所以可以取出指针变量在内存中的位置--地址。指针变量在内存中占4个字节的内存空间。 指针可以根据指针类型对地址对应的数据进行解析。由于每种数据类型在内存中占用的空间不同,指针只保存了存放数据的首地址,而没有指明在哪里结束。这时,就需要根据对应的类型,来寻找解释数据的结束地址。

###1、各种指针的工作方式

我写了一段测试代码

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字节数据 12345678h0040D778 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        ecx0040D797 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        eax0040D7AB 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        edx0040D7BF 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

程序输出的结果:

123456780000007800005678

变量nVar在内存中的数据为 “78 56 23 12” ,首地址从78开始。 所以才得到上面这种结果。

###2、引用 引用在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        ecx00401089 E8 7C FF FF FF       call        @ILT+5(Add) (0040100a)0040108E 83 C4 04             add         esp,417:       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,100401040 8B 55 08             mov         edx,dword ptr [ebp+8]00401043 89 0A                mov         dword ptr [edx],ecx10:   }

##常量

常量数据在程序运行前就已经存在,它被编译到可执行文件中。程序运行后,他们会被加载进来。这些数据通常都会在常量数据区中保存,该section的属性是没有写权限的,所以对常量修改时,程序会报错。

常量数据的地址减去基址,便是它在文件中的偏移地址。

如果,常量字符串的首地址为 0x00423FA8 ,该程序的基址为 0x00400000,所以对应的偏移地址为:字符串首地址 - 基地址 = 0x00023FA8 ,使用十六进制查看器打开可执行文件,到 0x00023FA8 处,可以看到该字符串。

###常量的定义

#define NUMBER_ONE    1const int nVar = NUMBER_ONE;

#define 是一个真常量,而const却是由编译器判断实现的变量,是一个假常量。 在实际应用中,使用const定义的常量,最终还是一个变量,只是在编译器内进行了检查,发现有修改则报错。

修改const常量:

const int nConst     = 5;int *pConst          = (int*)&nConst;*pConst              = 6;int nVar             = nConst;

转载请注明出处:

转载于:https://my.oschina.net/huangsz/blog/286245

你可能感兴趣的文章
String的属性和方法
查看>>
hdu-1800
查看>>
读写锁-锁粒度
查看>>
C#_delegate - 用委托实现事件,Display和Log类都使用Clock对象
查看>>
java.net.BindException: 权限不够
查看>>
《c程序设计语言》读书笔记-字符型0-9转为数字0-9
查看>>
公务员队伍开始动荡了吗?
查看>>
项目中解决实际问题的代码片段-javascript方法,Vue方法(长期更新)
查看>>
sdut 1500 Message Flood(Trie树)
查看>>
CSharp设计模式读书笔记(12):享元模式(学习难度:★★★★☆,使用频率:★☆☆☆☆)...
查看>>
Java内存泄露原因详解
查看>>
2017四月TOP100电商类App排行榜出炉
查看>>
配置struts2拦截器
查看>>
互联网“平滑数据迁移”架构技术实践
查看>>
SqlServer2005 性能调校之 利用Sql Server Profiler捕捉阻塞事件
查看>>
云时代架构读后感
查看>>
Java GC性能优化实战
查看>>
iOS开发线程之NSThread
查看>>
C语言断言
查看>>
Java设计模式之职责链设计模式
查看>>