1、 问题发现:
在涉及到共用体内存冲刷问题时,编程验证,初次发现下面问题:
#include "stdafx.h"
#include <iostream>
using namespace std;
uniondatatype
{
double a;
int b;
}x,y;
voidmain()
{ x.a=8;
x.b=4;
y.b=4;
y.a=8;
cout<<"x.a="<<x.a<<" "<<"x.b="<<x.b<<endl;
cout<<"y.a="<<y.a<<" "<<"y.b="<<y.b<<endl;
cout<<"the size of union is:"<<sizeof(datatype)<<endl;
cout<<"the size of a is:"<<sizeof(double)<<endl;
cout<<"the size of b is:"<<sizeof(int)<<endl;
system("pause");
}
运行结果:
很直观的疑问在于,当对共用体中x.a赋值为double型8后,再给x.b赋值为int型4,在最后输出应该是以后赋值的为准,而先前赋值的8应该被冲刷掉,但仍然能打印出来。本来原先的数据是没必要再去关心的,但还是一探究竟:
2、问题分析与解决:
上网查找double型(或float)数据以及int型在内存中的存放方式:
目前C/C++编译器标准都遵照IEEE制定的浮点数表示法来进行float,double运算。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。下面是具体的规格:
````````符号位 阶码 尾数 长度
float 1 8 23 32
double 1 11 52 64
由于通常C编译器默认浮点数是double型的,下面以double为例:
共计64位,折合8字节。由最高到最低位分别是第63、62、61、……、0位:
最高位63位是符号位,1表示该数为负,0正;
62-52位,一共11位是指数位;
51-0位,一共52位是尾数位。
在浮点型的数据在内存中的存放方式比较特殊,我们将其原先的整形8改为8.4进行探讨:
x.a=8.4;
x.b=4;
y.b=4;
y.a=8.4;
运行结果:
,从这里可以看到浮点数和整形数的明显区别。可以看到在x中,后来的int型对前面的double数据产生影响,使其显示为近似值,而在共用体y中,后来的double直接覆盖掉先入的int型,y.b显示的是随机数。但在这里,还是不知道是如何影响的,打算打开内存,一看究竟:
// 内存打印.cpp :定义控制台应用程序的入口点。
#include "stdafx.h"
#include "stdafx.h"
#include <iostream>
#include <bitset>
using namespace std;
uniondatatype
{
double a;
int b;
}x,y;
voidwatch_mem(char* addr,intlen)
{
for(int i = 0;i <len;++i)
{
printf("%X\t",*(addr+i));
}
printf("\n");
union datatype* data = (uniondatatype*)addr;
printf("a:%f\tb:%d.\n\n",data->a,data->b);
}
int_tmain(int argc, _TCHAR* argv[])
{
x.a=8.4;
watch_mem((char *)&x,sizeof(datatype));
x.b=4;
watch_mem((char *)&x,sizeof(datatype));
y.b=4;
watch_mem((char *)&y,sizeof(datatype));
y.a=8.4;
watch_mem((char *)&y,sizeof(datatype));
cout<<"x.a="<<x.a<<" "<<"x.b="<<x.b<<endl;
cout<<"y.a="<<y.a<<" "<<"y.b="<<y.b<<endl;
cout<<"the size of unionis:"<<sizeof(datatype)<<endl;
cout<<"the size of a is:"<<sizeof(double)<<endl;
cout<<"the size of b is:"<<sizeof(int)<<endl;
system("pause");
}
运行结果:
可以看到,浮点型的8.4在内存中存放为:
FFFFFFCD FFFFFFCC FFFFFFCC FFFFFFCC FFFFFFCCFFFFFFCC 20 40,接着输入int型的4,只占用掉前面的4个字节,变成:4 0 0 0 FFFFFFCC FFFFFFCC 20 40,这时再要打印b时,只提取了自己存放位置的数据,为4 0 0 0 ,在c++编译器中,数据按照高位存储,倒过来就是0 0 0 4,因此最后显示为4.而原先的数据后四位仍然存在,当调用显示x.a时,将整个数据装换成double型,这里我们以8.4为例,看double是如何存储的:
8.4,分解为整数部分和小数部分,整数部分8,二进制位:1000,小数部分:0.4化为二进制:011001100110……这个就是小数表示在计算机中的无限循环问题,无法精确表示,根据double的精度,小数最后保留53位。
8.4(10)=1000.01100110 0110……(2)=(浮点型)1.0000110 0110 0110…*2^3
现在看,整个浮点型double数据格式:阶符(1位)+阶码(11位)+尾数(53位)
阶码,一共11位,可以表示范围是-1024 ~ 1023。因为指数可以为负,为了便于计算,规定都先加上1023,在这里,3+1023=1026,表示成二进制:10000000010:;符号位为0(0正1负),在这里还有个问题就是隐藏技术,我们将科学计数表示的数小数点前的1隐藏,不存储,所以整个数据最后为:
01000000 00100000 11001100 11001100 11001100 11001100 11001100 11001100
转换为十六进制:
40 20 CC CC CC CC CC CC
在内存存储时,vs编译器采用高位存储(大端存储)方式,倒过来:
CC CC CC CC CC CC 20 40;
和运行结果比较:FFFFFFCDFFFFFFCC FFFFFFCC FFFFFFCC FFFFFFCC FFFFFFCC 20 40,可以看出在有效数据部分基本一致,但多出一些FFFFFF,继续打开调试内存,
发现里面存放的又是转换成的ascii码值,两者为何有别,有点糊涂了。
转为十六进制:
进一步打开内存:
看到在内存中存储的确实是我们转换过的数据,而没有FFFFFF。感觉问题不是出在内存,而是在打印函数printf。单独测试将-1打印出来,为FFFFFFFF,接着将结果按照%d打印出:
看来编译器是首先读取到CC,然后再将其认为是-52,在printf打印时,将负数按照32位表示出来并显示。
这里还有待确认的问题,就是实际内存分配时,当指针加1时,移动的位置是按照编译系统的字(32位)来移动,还是按照一个字节(8位)来移动。