int _tmain(int argc, _TCHAR* argv[])
{
// 一、结构体内存对齐
// 内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。
//但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。
//如果你想了解更加底层的秘密,就必须了解“内存对齐”。
// 需要进行内存对齐的原因:
//1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;
//某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
//2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
//原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
#pragma pack(8);// VC默认结构体内存对齐参数是8
struct tagTest
{
char c;
double d;
short s;
int i;
}testData;
testData.c = '1';
testData.d = 2.0f;
testData.s = 3;
testData.i = 4;
printf("sizeof(testData):%d \n&c:%d \n&d:%d \n&s: %d \n&i: %d\n", sizeof(testData), &testData.c, &testData.d, &testData.s, &testData.i);
// 1.结构体内存对齐之基本原则:
// 1).整体与对齐参数:基本类型结构体,全部成员是顺序排列的从底地址到高地址,单成员类型就是按照本类型补码浮点或数值小端形式存放;
// 2).内部成员实际对齐参数n:内部每个成员进来的时候是以min(N,成员)作为实际内存对齐参数来排列内存的,N是结构体内存对齐参数由#pragma pack(8)决定默认是8,
// 第一个是系统找一个以对齐参数N倍数的地址为初地址(首地址偏移为0),后面的地址依次用min(N,本成员)作为实际对齐参数,该参数首个倍数地址(从首地址偏移)作为成员内存地址;
// 3) 内存对齐最终大小max(n1,n2,n3,...):是最大元素的整数倍,不够后面就补0字节。
// 2.结构体内存对齐之特殊情况:
// 1).指针和嵌套结构体(拆分):包含指针和嵌套结构体的结构体,包含的指针无论是一维还是二维都是4字节(32bit机器,64bit是8字节),
// 嵌套结构体是作为一个递归成员,内部成员自身对齐,内部与外部之间将内部最大作为n最终取min(n,N),外部与内部之间用的就是外部的成员min(n,N)。
// 2) 数组成员(拆分):包含数组时候,整个数组不是作为一个成员,而是里面的每个元素作为一个结构体成员。
// 3) 有位段时的对齐规则(组合):同类型的、相邻的可连续在一个类型的存储空间中存放的位段成员作为一个该类型的成员变量来对待,
// 不是同类型的、相邻的位段成员,分别当作一个单独得该类型的成员来对待,分配一个完整的类型空间,
// 其长度为该类型的长度,其他成员的分配规则不变,仍然按照前述的对齐规则进行。
// 3.定义结构体时候需要注意下排列,使得消耗内存比较少,和合理的使用结构体内存对齐参数优化内存占用和提高访问速度
// 可以用pragma pack(4)设置对齐参数(也可以在VC工程属性C/C++ Code Generation中设置,默认是8字节,可设置为1,2,4,8,16字节)。
// 应用:结构体读取网络和文件数据:
// 结构体类型解析数据,例如从文件中直接读取到结构体中(因为写入和读取二进制时候都是相同的结构体内存对齐模式,所以字节赋值和转换值会成功)
// 结构体嵌套例子:
struct s1
{
char c;
double l;
}stData1;
struct s2
{
char a;
s1 b;// 分配b的s1的c,此时用内部结构体最大对齐大小8,作为内存对齐大小。
int c;
}stData2;
printf("嵌套结构体例子: s1字节:%d, s2字节: %d, b得a距离:%d \n", sizeof(stData1),sizeof(stData2), int(&stData2.b) - int(&stData2.a));
// 数组例子:
struct tagArray
{
char c;
double d;
int num[4];
}array1;
printf("数组内存结构体大小:%d, d到num距离: %d\n", sizeof(tagArray), int(&(array1.num[0])) - int(&array1.d));
//位域例子:
/*使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字
段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字
段将从新的存储单元开始,其偏移量(首地址)为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方
式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。*/
struct tagField
{
int a:5;
int b:9;
char c;
short d;
}testField;
printf("位域结构体大小:sizeof(testField): %d a到c的距离: %d\n", sizeof(testField), int(&testField.c) - int(&testField));// 大小为8
testField.a = 12;
testField.b = 99;
testField.c = 'c';
testField.d = 199;
printf("a:%d, b:%d, c:%c, d:%d\n", testField.a, testField.b, testField.c, testField.d);
// 二、结构体内存类型间递增分布,类型内小端补码编码细节:
char szHex[256];
unsigned char *pValue = (unsigned char*)&testData;
int nValue = 0;
nValue |= (int)(*(pValue + 19));
nValue << 8;
nValue |= (int)(*(pValue + 18));
nValue << 8;
nValue |= (int)(*(pValue + 17));
nValue << 8;
nValue |= (int)(*(pValue + 16));
sprintf_s(szHex, 256, "char地址: %d, 十六进制值: %02x, 值:%c; int地址:%d, 十六进制值: %02x%02x%02x%02x, 值: %d",
&testData.c, unsigned char(*pValue), char(*pValue),
&testData.i, unsigned char(*(pValue + 16)),unsigned char(*(pValue + 17)),unsigned char(*(pValue + 18)),unsigned char(*(pValue + 19)), nValue );
printf("结构体二进制 szHex:%s\n", szHex);
//sprintf_s(szHex, 256, "十六进制:%02x", unsigned char((*pValue + 19)));
// testData结构体里面整体数据是从底字节到高字节顺序存放,所以char地址是底的,十六进制ASCII编码是:0x31,值是1;
// int是高地址,十六进制补码小端模式表示为:0x04000000;值是4。其它成员同理。
struct bit
{
int a:3;
int b:2;
int c:3;
};
bit bData;
char *pChar = (char*)&bData;
*pChar = 0x99;
cout<<"Itel题目: a:"<<bData.a<<" b:"<<bData.b<<" c:"<<bData.c<<endl;
// 输出为1,-1,4;因为0x99的二进制为10011001,看做一个Int按照书写到小端模式从底地址存储为:100 11 001,
// 读取时因为需要转换为int,小端到书写模式是:00000001补码的原码为1;11111111补码的原码-1,00000100补码的原码为 4。
struct example1
{
short a;
long b;
};
struct example2
{
char c; // 占1字节,填充3字节
example1 struct1; // 占8字节
short e; // 占2字节,填充2字节
};
example2 e2;
int d=(unsigned int)&e2.struct1-(unsigned int)&e2.c;
printf("Microsoft: %d,%d,%d\n",sizeof(example1),sizeof(example2),d);
#pragma pack(8)
struct test_t {
int a; /* 长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */
char b; /* 长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
short c; /* 长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */
char d; /* 长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8] */
};
printf("test_t大小:%d\n", sizeof(test_t));// 大小为12, 没有对齐边界的说法,只有最后的填充,用取模的方法和偏移倍数的方法是一样的。
while(1);
return 0;
}
//参考文档:
//http://wenku.baidu.com/view/aa39eec789eb172ded63b716.html
{
// 一、结构体内存对齐
// 内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。
//但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。
//如果你想了解更加底层的秘密,就必须了解“内存对齐”。
// 需要进行内存对齐的原因:
//1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;
//某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
//2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
//原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
#pragma pack(8);// VC默认结构体内存对齐参数是8
struct tagTest
{
char c;
double d;
short s;
int i;
}testData;
testData.c = '1';
testData.d = 2.0f;
testData.s = 3;
testData.i = 4;
printf("sizeof(testData):%d \n&c:%d \n&d:%d \n&s: %d \n&i: %d\n", sizeof(testData), &testData.c, &testData.d, &testData.s, &testData.i);
// 1.结构体内存对齐之基本原则:
// 1).整体与对齐参数:基本类型结构体,全部成员是顺序排列的从底地址到高地址,单成员类型就是按照本类型补码浮点或数值小端形式存放;
// 2).内部成员实际对齐参数n:内部每个成员进来的时候是以min(N,成员)作为实际内存对齐参数来排列内存的,N是结构体内存对齐参数由#pragma pack(8)决定默认是8,
// 第一个是系统找一个以对齐参数N倍数的地址为初地址(首地址偏移为0),后面的地址依次用min(N,本成员)作为实际对齐参数,该参数首个倍数地址(从首地址偏移)作为成员内存地址;
// 3) 内存对齐最终大小max(n1,n2,n3,...):是最大元素的整数倍,不够后面就补0字节。
// 2.结构体内存对齐之特殊情况:
// 1).指针和嵌套结构体(拆分):包含指针和嵌套结构体的结构体,包含的指针无论是一维还是二维都是4字节(32bit机器,64bit是8字节),
// 嵌套结构体是作为一个递归成员,内部成员自身对齐,内部与外部之间将内部最大作为n最终取min(n,N),外部与内部之间用的就是外部的成员min(n,N)。
// 2) 数组成员(拆分):包含数组时候,整个数组不是作为一个成员,而是里面的每个元素作为一个结构体成员。
// 3) 有位段时的对齐规则(组合):同类型的、相邻的可连续在一个类型的存储空间中存放的位段成员作为一个该类型的成员变量来对待,
// 不是同类型的、相邻的位段成员,分别当作一个单独得该类型的成员来对待,分配一个完整的类型空间,
// 其长度为该类型的长度,其他成员的分配规则不变,仍然按照前述的对齐规则进行。
// 3.定义结构体时候需要注意下排列,使得消耗内存比较少,和合理的使用结构体内存对齐参数优化内存占用和提高访问速度
// 可以用pragma pack(4)设置对齐参数(也可以在VC工程属性C/C++ Code Generation中设置,默认是8字节,可设置为1,2,4,8,16字节)。
// 应用:结构体读取网络和文件数据:
// 结构体类型解析数据,例如从文件中直接读取到结构体中(因为写入和读取二进制时候都是相同的结构体内存对齐模式,所以字节赋值和转换值会成功)
// 结构体嵌套例子:
struct s1
{
char c;
double l;
}stData1;
struct s2
{
char a;
s1 b;// 分配b的s1的c,此时用内部结构体最大对齐大小8,作为内存对齐大小。
int c;
}stData2;
printf("嵌套结构体例子: s1字节:%d, s2字节: %d, b得a距离:%d \n", sizeof(stData1),sizeof(stData2), int(&stData2.b) - int(&stData2.a));
// 数组例子:
struct tagArray
{
char c;
double d;
int num[4];
}array1;
printf("数组内存结构体大小:%d, d到num距离: %d\n", sizeof(tagArray), int(&(array1.num[0])) - int(&array1.d));
//位域例子:
/*使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字
段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字
段将从新的存储单元开始,其偏移量(首地址)为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方
式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。*/
struct tagField
{
int a:5;
int b:9;
char c;
short d;
}testField;
printf("位域结构体大小:sizeof(testField): %d a到c的距离: %d\n", sizeof(testField), int(&testField.c) - int(&testField));// 大小为8
testField.a = 12;
testField.b = 99;
testField.c = 'c';
testField.d = 199;
printf("a:%d, b:%d, c:%c, d:%d\n", testField.a, testField.b, testField.c, testField.d);
// 二、结构体内存类型间递增分布,类型内小端补码编码细节:
char szHex[256];
unsigned char *pValue = (unsigned char*)&testData;
int nValue = 0;
nValue |= (int)(*(pValue + 19));
nValue << 8;
nValue |= (int)(*(pValue + 18));
nValue << 8;
nValue |= (int)(*(pValue + 17));
nValue << 8;
nValue |= (int)(*(pValue + 16));
sprintf_s(szHex, 256, "char地址: %d, 十六进制值: %02x, 值:%c; int地址:%d, 十六进制值: %02x%02x%02x%02x, 值: %d",
&testData.c, unsigned char(*pValue), char(*pValue),
&testData.i, unsigned char(*(pValue + 16)),unsigned char(*(pValue + 17)),unsigned char(*(pValue + 18)),unsigned char(*(pValue + 19)), nValue );
printf("结构体二进制 szHex:%s\n", szHex);
//sprintf_s(szHex, 256, "十六进制:%02x", unsigned char((*pValue + 19)));
// testData结构体里面整体数据是从底字节到高字节顺序存放,所以char地址是底的,十六进制ASCII编码是:0x31,值是1;
// int是高地址,十六进制补码小端模式表示为:0x04000000;值是4。其它成员同理。
struct bit
{
int a:3;
int b:2;
int c:3;
};
bit bData;
char *pChar = (char*)&bData;
*pChar = 0x99;
cout<<"Itel题目: a:"<<bData.a<<" b:"<<bData.b<<" c:"<<bData.c<<endl;
// 输出为1,-1,4;因为0x99的二进制为10011001,看做一个Int按照书写到小端模式从底地址存储为:100 11 001,
// 读取时因为需要转换为int,小端到书写模式是:00000001补码的原码为1;11111111补码的原码-1,00000100补码的原码为 4。
struct example1
{
short a;
long b;
};
struct example2
{
char c; // 占1字节,填充3字节
example1 struct1; // 占8字节
short e; // 占2字节,填充2字节
};
example2 e2;
int d=(unsigned int)&e2.struct1-(unsigned int)&e2.c;
printf("Microsoft: %d,%d,%d\n",sizeof(example1),sizeof(example2),d);
#pragma pack(8)
struct test_t {
int a; /* 长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */
char b; /* 长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */
short c; /* 长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */
char d; /* 长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8] */
};
printf("test_t大小:%d\n", sizeof(test_t));// 大小为12, 没有对齐边界的说法,只有最后的填充,用取模的方法和偏移倍数的方法是一样的。
while(1);
return 0;
}
//参考文档:
//http://wenku.baidu.com/view/aa39eec789eb172ded63b716.html