C语言14-结构体内存布局及对齐、共用体
内存断点
现在想确认“什么时候g_Brick中的数据会被修改”。
可以使用内存断点,更快地解决这个问题。
所谓内存断点(数据断点)其实就是可以对内存中某块数据进行监视,如果程序有对该数据进行访问的行为,那么触发该断点。
如何设置
在“调试状态下”,在调试菜单中“新建断点”->“新建数据断点”。
VS中生成静态版本的exe
到目前为止,VS默认的生成exe都是使用的动态库。它的好处是体积小。
还可以生成静态库版本的exe,这样通用性更好。
结构体的内存布局
结构体是非基本数据类型,那么他的内存布局是怎样的呢?
先从结构体的大小来观察下:
struct tagStruct1
{
int nValue1;
int nValue2;
int nValue3;
int nValue4 ;
};
int main(int argc, char* argv[])
{
printf("%d\r\n", sizeof(tagStruct1));
tagStruct1 Str1;
Str1.nValue1 = 0x11111111;
Str1.nValue2 = 0x22222222;
Str1.nValue3 = 0x33333333;
Str1.nValue4 = 0x44444444;
return 0;
}
(暂时)可以看出,结构体的大小,就是所有成员大小之和。
那么,剩下的问题就是只有:成员在内存中的放置顺序是如何的?
经验证:
得出结论:
- 结构体的大小,就是所有成员的总和
- 先定义的成员,出现咋结构体较低的地址处
结构体指针到底是如何寻址的
struct tagStruct1
{
int nValue1;
int nValue2;
int nValue3;
int nValue4;
};
void Find(tagStruct1* ptr)
{
printf("%p\r\n", &ptr->nValue1);
printf("%d\r\n", ptr->nValue2);
}
tagStruct1 g_struc;
int main(int argc, char* argv[])
{
tagStruct1* obj = &g_struc;
obj->nValue2 = 3;
Find(obj);
return 0;
}
对于以上的函数,参数只有一个指针(结构体的首地址)。凭什么可以通过这个首地址,找到各个成员的值呢?
- 根据首地址和头文件中结构体的定义,找到成员的首地址
- 根据头文件中记录的成员的解释方式,将上一步的地址中的值取出
结构体内存对齐
以上,我们已经了解了结构体内存布局的基本核心。
但是,这不足以解释结构体布局的所有方面,比如:
struct tagStruct1
{
double dbValue1;
char chValue2;
int nValue;
};
struct tagStruct2
{
char chValue2;
double dbValue1;
int nValue3
};
int main (int argc, char* argv[])
{
printf("%d\r\n", sizeof(tagStruct1));
printf("%d\r\n", sizeof(tagStruct2));
return 0;
}
两个结构体大小的输出,是不一样的。这就是:结构体对齐问题
结构体对齐问题的起源
早期的计算机中,为了效率,会对数据在内存中存在的首地址,有一定要求。
比如,int存在的首地址,必须要在4的倍数上。
double存在的首地址,必须在8的倍数上。
以上这样做,是为了存储和读取效率。
这种习惯,依然沿用了C语言编译器的默认选项中。
对于结构体的对齐(VS默认配置),要求如下:
- 结构体第一个成员的偏移为0(永远是对齐的)
- 之后的成员,其偏移都必须是自己sizeof(type)的整数倍
- 结构体本身也是有对齐的要求:我们想象,当前结构体之后,会紧接一个同类型的结构体。那个同类型的结构体的偏移的对齐值是:结构以中所有成员对齐值的最大值。
- 实际上,还要考虑工程上对齐值的设置。所有对齐值的选择中,应该是“对齐规则中的对齐值”和“工程设置的对齐值”,两者取较小者。
struct tagTest2
{
char chValue2;//offset 0, size: 1
double dbValue1;//offset 8, size : 8
int nValue3;//offset 16, size: 4
};
//因为结构体本身的对齐值是sizeof(double) = 8.所以最后的结构体大小,不是20,而是24.
共用体
共用体通结构体类似,都是非基本数据类型,所以需要先声明公用体新类型,在用新类型去定义新变量。
公用体的关键字是union。
union UnionBlackBoard
{
double dbSalary;
char szName[8];
int nNoticeID;
};
int main(int argc, char* argv[])
{
UnionBlackBoard unionObj;
unionObj.dbSalary = 1000;
printf("%lf\r\n", unionObj.dbSalary);
unionObj.nNoticeID = 17;
printf("%d\r\n", unionObj.nNoticeID);
strcpy(unionObj.szName, "Zhang");
printf("%s\r\n", unionObj.szName);
return 0;
}
union的使用和结构体有类似之处。
同样也有不同之处,更加重要的是主要体现在内存布局上:
- struct中, 各个成员的内存区域嘞,是不重叠的
- union中,所有成员,公用同一块内存空间,所有成员的首地址重合。
union的应用
如何快速地转换float类型和其对应的16进制
union TransferFloat
{
float fltValue;
unsigned char chAry[4];
}
int main (int argc, char* argv[])
{
TransferFloat myunion;
myunion.fltValue=3.14;
for (szie_t i=0;i<4;i++)
{
printf("%02X", myunion.chAry[i]);
}
return 0;
}
union在IP转换上的应用
什么是IP?
IPV4的IP,其实就是一个int数字而已。只不过为了人方便读取,才发明了点的记录方法(172.254.112.3)(注意,每三位数的最大值不操过255)
union IPV4IP
{
int nValue;
unsigned char chAry[4];
};
上一篇: C语言11-词法分析器