C++进阶3.字节对齐 联合 20131011
多益和金山笔试 知识漏洞 20131011
前言:
今天下午是多益网络的笔试,整体感觉还好,但是找到很多的知识漏洞。一直笔试到6:00,然后紧张的从会生活区嗑了点饭,然后骑车立马冲到华工参加金山的笔试。(华工没有什么认识的人,微信上认识一个看头像和照片挺漂亮的女生,本来想笔试结束的时候见一下,可惜微信她,直到回到学校才回我,下次吧!)回到正题上,笔试上有很多的知识漏洞,所以整理一下。
1.联合的知识
题目是这样的:
union { int val; char str[2];} obj;
main:
obj.str = 10;
obj.str = 1;
cout << obj.val << endl; 先说一下答案, pow(2,8) * 1 + 10 = 266;
首先看一下联合的知识:
联合允许使用多种类型来引用一个对象。联合声明的语法和结构体是一样的,只是语义相差比较大而已,联合中在同一个时间只能存储一个成员数据(即只有一个数据是活跃的),因此当我们访问数据的时候,最多一个数据是正确的。
联合的内存大小是取决于其中字节数最多的成员,而不是累加,联合也会使用字节对其的,联合中存储的数据完全取决于他的解释方式。使用一个成员变量存入数据,然后使用另一个成员变量获取他们的值,但是结果可能不是你想要的。在定义联合变量的时候,可以指定他的初始值,但是只能够指定一个初始值。而且该联合的初始值类型只能够和第一个成员变量类型相匹配。可以获取联合变量的地址,也可以取一个联合变量的任意一个对象的地址,他们总是相等的。
还可以在同类型的变量之间赋值,但是不能够比较两个union变量的大小,不只因为肯能存在填补字节,而且这两个变量可能存储着不同类型的成员。实际上代表了两种不同的变量。
C++中对C中的union进行了扩展,除了数据成员之外,还可以定义成员的访问说明符,也可以定义成员函数,甚至是构造函数和析构函数。但是不可以包含虚拟的成员函数和静态数据成员,不可以作为其他类型的基类或者是派生自其他类型。(也就是处理继承机制、静态、虚拟之类的,其他的关于C++class的都是可以使用的)。
回来通过代码学习联合:
#include <iostream>
using namespace std;
union A{
int val;
char obj[2];
}str;
int main()
{
char ch[2] ={'a','b'};
printf("%c\n" , *ch);
printf("%s\n",ch);
cout << ch << "#end"<<endl;
/*
对于匿名的联合str,初始化的时候将所有的内存置为0,
对于联合直接声明的对象,必须初始化,否则会编译出错,提示没有初始化的联合;
*/
cout << "匿名联合对象 初始化值:" <<str.val << endl;
A feiNiMing;
feiNiMing.obj[0] = 'a';//不写的话,编译出错提示未初始化的联合
cout << "非匿名联合对象初始化值:" << feiNiMing.val << endl;
/*
对联合进行赋值,存储都是从低字节开始,然后放进内存中
*/
str.obj[0] = 10;
str.obj[1] = 1;
cout << str.val << endl; //内存中的四字节是0 0 1 10(从高位到低位) ,然后就是转换成int 为 266
cout << "str.obj[0]:" << (int)str.obj[0] << endl;// 10
cout << "str.obj[1]: " << (int)str.obj[1] << endl;// 1
str.obj[0] = 97;// char 'a'
str.obj[1] = 98;// char 'f'
cout <<"str.obj: " << str.obj << "#end" << endl;// 后面是0 对应的ascii '\0'字符,所以结束输出
cout << endl;
/*所有成员的地址都是联合对象的地址,这些输出是相同的*/
cout << "&str :" << (int)&str << endl;
cout << "&str.val:" << (int)&(str.val) << endl;
cout << "&str.obj:" << (int)&(str.obj) << endl;
cout << endl;
/*通过指针操作联合内存*/
printf("obj[0] address : %d\n", (char*)(&str) );
printf("obj[0] content: %d\n", *(char*)(&str) ); //97
printf("obj[1] address : %d\n", ((char*)(&str)+1) );
printf("obj[1] content: %d\n", *((char*)(&str)+1) );// 98
printf("obj[2] address : %d\n", ((char*)(&str)+2) );
printf("obj[2] content: %d\n", *((char*)(&str)+2) );//0
*((char*)(&str)+1) = 'k';
cout << "str.obj[1]: "<< str.obj[1] << endl;
return 0;
}
2.位域和自己对齐的问题
这一次考试中考到了很多关于sizeof的问题,也就是字节对齐的问题。还有struct 中的数据对齐,位域情况,关于字节对齐,是由一定的了解,但是对于位域,自己从来没有接触过,整理一下。
首先是位域的知识:显然在多数情况下,使用字节为基本单位的存储模式会浪费掉大量的内存空间。如果经常使用bool类型的变量的话,浪费成都会达到87.5%。在进行嵌入式系统开发的时候,提供的内存空间有限,就要对内存空间精打细算了,这种情况下我们可以使用位域和位运算来解决此问题。
位域是以单个的位(bit)为单位设计的一个struct的存储空间,因此可以根据数据成员的有效范围仔细规划他们所需要的位数。
struct DateTime{
unsigned int year ; // 4 byte = 32 bit
unsigned int month :4; // 4 bit < 16
unsigned int day :5; // 5 bit < 32
unsigned int hour :5;
unsigned int minute :6;// 6 bir < 64
unsigned int second :6;
/*后面的数据都是按照bit开始非配的,在最后面的时候,需要考虑对齐 4 + (4+5+5+6+6)/8 取上界 并且对齐 8*/
};
在C中位域的成员只可以是int, unsigned int, signed int;在C++中允许使用的还有char short等等。不允许使用指针类型的或者是浮点数类型的作为位域的数据成员类型.因为可能导致无效的值.此外对于signed int 因为正负号要占用一位,因此该类型的位域成员变量长度至少是2.同时对于unsigned int year :33;也是不正确的,因为超出了范围。
在位域中,同时可以定义非具名的位域成员,作用就是相当于占位符,用于隔离两个相邻的位域成员,但是因为他没有名字,是不可以直接访问的,示例如下:
struct T
{
unsigned int day :5;
unsigned int :2;
unsigned int hour :5;
/*这种占位符仅仅占用了两个bit的空间,将上面两个数据隔开,大小的话是12 bit 对齐的话int 是4 byte*/
};
同时可以定义长度是0 的位域空间,不过这个时候的作用就是让我们的计算机分配内存的时候,从下一个完整的word开始分配内存空间。
struct T
{
unsigned int day :5;
unsigned int :0;
unsigned int hour :5;
/*这种占位符仅仅占用了两个bit的空间,将上面两个数据隔开,从另一个字长分配空间 对齐的话int 是2*4 byte*/
};
另外还有一些需要注意的知识点关于位域的:
1)在修改数据成员的时候,要防止修改位域成员的值时出现的向上溢出的情况;
2)不能够对位域中的数据成员取址,即使该成员完全和字节边对齐,因为字节是编址的最小单位。但是我们可以取位域对象的地址,即使所有成员的位数总和达不到字节的倍数,位域对象也会对齐到机器字长。 即不可以 &t.hour 不可以对位域对象取址;
3)不要把位域对象当做数组,不可以使用[]进行访问。运算最好属于偶那个~,& ! | << >> ^运算符,放置溢出。
前面的DateTime的比较好的设计方案是:
struct DateTime{
unsigned int year ; // 4 byte = 32 bit
unsigned int month :8; // 4 bit < 16
unsigned int day :8; // 5 bit < 32
unsigned int hour :8; s
unsigned int minute :8;// 6 bir < 64
unsigned int second :8;
/*后面的数据都是按照bit开始非配的,在最后面的时候,需要考虑对齐取上界 并且对齐 12*/
};
字节对齐的问题:
本以为自己对于字节对齐十分掌握了,但是今天的多益笔试,让自己彻底挂了,也不叫彻底挂了吧,自己曾经复习过这一类的知识,但是还是错了。所以还是认真细致的整理一遍。
结构成员的对齐方式是否一致,将影响到整个程序运行期间的稳定性和正确性,特别是模块和模块之间的公用的数据类型部分。
typedef unsigned char BYTE;
enum Color{RED = 0x01,BLUE,GREEN,YELLOW,BLACK};
struct Sedan
{
bool m_hasSkylight;
Color m_color;
bool m_isAutoShitf;
double m_price;
BYTE m_seatNum;
/* 考虑字节对齐的话,占用的空间大小是 32 字节*/
};
CPU对对象的访问效率与地址的关系:
对于复合类型的数据结构,如果他的起始地址能够满足其中要求最严格的(最高的)那个数据成员的自然对齐要求,那么他就是自然对齐的;如果有些数据成员也是符合类型的数据结构,则依次类推知道所有的数据成员都是基本的数据类型。
struct Car{
double price;
bool youhuo;
};
struct MyCar
{
char start;
Car name;
bool canRun;
};
这个时候MyCar的大小是按照最严格的的double,是8进行对齐,sizeof(MyCar)是32。都是按照基本类型进行对齐的。
追梦的飞飞
于广州中山大学 2013102
HomePage: http://yangtengfei.duapp.com