struct和unionWord文件下载.docx
《struct和unionWord文件下载.docx》由会员分享,可在线阅读,更多相关《struct和unionWord文件下载.docx(21页珍藏版)》请在冰豆网上搜索。
注意,printf的%u转换说明表示无符号数,sizeof的值是size_t类型的,是某种无符号整型。
为什么编译器要这样处理呢?
有一个知识点我此前一直回避没讲,那就是大多数计算机体系统结构对于访问内存的指令是有限制的,在32位平台上,访问4字节的指令(比如上面的movl)所访问的内存地址应该是4的整数倍,访问两字节的指令(比如上面的movw)所访问的内存地址应该是两字节的整数倍,这称为对齐(Alignment)。
以前举的所有例子中的内存访问指令都满足这个限制条件,读者可以回头检验一下。
如果指令所访问的内存地址没有正确对齐会怎么样呢?
在有些平台上将不能访问内存,而是引发一个异常,在x86平台上倒是仍然能访问内存,但是不对齐的指令执行效率比对齐的指令要低,所以编译器在安排各种变量的地址时都会考虑到对齐的问题。
对于本例中的结构体,编译器会把它的基地址对齐到4字节边界,也就是说,ebp-0x10这个地址一定是4的整数倍。
s.a占一个字节,没有对齐的问题。
s.b占两个字节,如果s.b紧挨在s.a后面,它的地址就不能是两字节的整数倍了,所以编译器会在结构体中插入一个填充字节,使s.b的地址也是两字节的整数倍。
s.c占4字节,紧挨在s.b的后面就可以了,因为ebp-0xc这个地址也是4的整数倍。
那么为什么s.d的后面也要有填充位填充到4字节边界呢?
这是为了便于安排这个结构体后面的变量的地址,假如用这种结构体类型组成一个数组,那么后一个结构体只需和前一个结构体紧挨着排列就可以保证它的基地址仍然对齐到4字节边界了,因为在前一个结构体的末尾已经有了填充字节。
事实上,C标准规定数组元素必须紧挨着排列,不能有空隙,这样才能保证每个元素的地址可以按“基地址+n×
元素大小”简单计算出来。
合理设计结构体各成员的排列顺序可以节省存储空间,例如上例中的结构体改成这样就可以避免产生填充字节:
struct{
chara;
chard;
shortb;
intc;
}s;
此外,gcc提供了一种扩展语法可以消除结构体中的填充字节:
}__attribute__((packed))s;
这样就不能保证结构体成员的对齐了,在访问b和c的时候可能会有效率问题,所以除非有特别的理由,一般不要使用这种语法。
以前我们使用的数据类型都是占几个字节,最小的类型也要占一个字节,而在结构体中还可以使用Bit-field语法定义只占几个bit的成员。
下面这个例子出自王聪的网站(www.wangcong.org):
例19.4.Bit-field
typedefstruct{
unsignedintone:
1;
unsignedinttwo:
3;
unsignedintthree:
10;
unsignedintfour:
5;
unsignedint:
2;
unsignedintfive:
8;
unsignedintsix:
}demo_type;
intmain(void)
demo_types={1,5,513,17,129,0x81};
sizeofdemo_type=%u\n"
sizeof(demo_type));
values:
s=%u,%u,%u,%u,%u,%u\n"
s.one,s.two,s.three,s.four,s.five,s.six);
s这个结构体的布局如下图所示:
图19.6.Bit-field的存储布局
Bit-field成员的类型可以是int或unsignedint,表示有符号数或无符号数,但不表示它像普通的int型一样占4个字节,它后面的数字是几就表示它占多少个bit,也可以像unsignedint:
这样定义一个未命名的Bit-field,即使不写未命名的Bit-field,编译器也有可能在两个成员之间插入填充位,如上图的five和six之间,这样six这个成员就刚好单独占一个字节了,访问效率会比较高,这个结构体的末尾还填充了3个字节,以便对齐到4字节边界。
以前我们说过x86的ByteOrder是小端的,从上图中one和two的排列顺序可以看出,如果对一个字节再细分,则字节中的BitOrder也是小端的,因为排在结构体前面的成员(靠近低地址一边的成员)取字节中的低位。
关于如何排列Bit-field在C标准中没有详细的规定,这跟ByteOrder、BitOrder、对齐等问题都有关,不同的平台和编译器可能会排列得很不一样,要编写可移植的代码就不能假定Bit-field是按某一种固定方式排列的。
Bit-field在驱动程序中是很有用的,因为经常需要单独操作设备寄存器中的一个或几个bit,但一定要小心使用,首先弄清楚每个Bit-field和实际bit的对应关系。
和前面几个例子不一样,在上例中我没有给出反汇编结果,直接画了个图说这个结构体的布局是这样的,那我有什么证据这么说呢?
上例的反汇编结果比较繁琐,我们可以通过另一种手段得到这个结构体的内存布局。
C语言还有一种类型叫联合体,用关键字union定义,其语法类似于结构体,例如:
例19.5.联合体
typedefunion{
unsignedintone:
unsignedinttwo:
unsignedintthree:
unsignedintfour:
unsignedint:
unsignedintfive:
unsignedintsix:
}bitfield;
unsignedcharbyte[8];
demo_typeu={{1,5,513,17,129,0x81}};
u=%u,%u,%u,%u,%u,%u\n"
u.bitfield.one,u.bitfield.two,u.bitfield.three,
u.bitfield.four,u.bitfield.five,u.bitfield.six);
hexdumpofu:
%x%x%x%x%x%x%x%x\n"
u.byte[0],u.byte[1],u.byte[2],u.byte[3],
u.byte[4],u.byte[5],u.byte[6],u.byte[7]);
一个联合体的各个成员占用相同的内存空间,联合体的长度等于其中最长成员的长度。
比如u这个联合体占8个字节,如果访问成员u.bitfield,则把这8个字节看成一个由Bit-field组成的结构体,如果访问成员u.byte,则把这8个字节看成一个数组。
联合体如果用Initializer初始化,则只初始化它的第一个成员,例如demo_typeu={{1,5,513,17,129,0x81}};
初始化的是u.bitfield,但是通过u.bitfield的成员看不出这8个字节的内存布局,而通过u.byte数组就可以看出每个字节分别是多少了。
C/C++中的联合union
减小字体
增大字体
联合(union)在C/C++里面见得并不多,但是在一些对内存要求特别严格的地方,联合又是频繁出现,那么究竟什么是联合、怎么去用、有什么需要注意的地方呢?
就这些问题,我试着做一些简单的回答,里面肯定还有不当的地方,欢迎指出!
1、什么是联合?
“联合”是一种特殊的类,也是一种构造类型的数据结构。
在一个“联合”内可以定义多种不同的数据类型,一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,已达到节省空间的目的(还有一个节省空间的类型:
位域)。
这是一个非常特殊的地方,也是联合的特征。
另外,同struct一样,联合默认访问权限也是公有的,并且,也具有成员函数。
2、联合与结构的区别?
“联合”与“结构”有一些相似之处。
但两者有本质上的不同。
在结构中各成员有各自的内存空间,一个结构变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。
而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。
应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。
3、如何定义?
例如:
uniontest
{
test(){}
intoffice;
charteacher;
};
定义了一个名为test的联合类型,它含有两个成员,一个为整型,成员名为office;
另一个为字符数组,数组名为teacher。
联合定义之后,即可进行联合变量说明,被说明为test类型的变量,可以存放整型量office或存放字符数组teacher。
4、如何说明?
联合变量的说明有三种形式:
先定义再说明、定义同时说明和直接说明。
以test类型为例,说明如下:
1)uniontest
uniontesta,b;
/*说明a,b为test类型*/
2)uniontest
}a,b;
3)union
经说明后的a,b变量均为test类型。
a,b变量的长度应等于test的成员中最长的长度,即等于teacher数组的长度,共5个字节。
a,b变量如赋予整型值时,只使用了4个字节,而赋予字符数组时,可用5个字节。
5、如何使用?
对联合变量的赋值,使用都只能是对变量的成员进行。
联合变量的成员表示为:
联合变量名.成员名
例如,a被说明为test类型的变量之后,可使用a.class、a.office
不允许只用联合变量名作赋值或其它操作,也不允许对联合变量作初始化赋值,赋值只能在程序中进行。
还要再强调说明的是,一个联合变量,每次只能赋予一个成员值。
换句话说,一个联合变量的值就是联合变员的某一个成员值。
6、匿名联合
匿名联合仅仅通知编译器它的成员变量共同享一个地址,而变量本身是直接引用的,不使用通常的点号运算符语法.例如:
#include<
iostream>
voidmain()
union{
inttest;
charc;
test=5;
c='
'
a'
;
std:
:
cout<
<
i<
"
"
c;
}
union在嵌入式编程中的3个妙用
在嵌入式系统中,一般不建议使用union结构,因为union结构中的各个成员之间存在相互影响,容易滋生问题。
可见,union也是把双刃剑。
懂得使用它的人可以做到“削铁如泥”,而不懂得使用它的人很可能会被其所伤。
下面介绍的几种方法都是嵌入式系统常用的几种技巧。
如果熟练掌握,将来定有所用。
1.all的使用
使用all的数据结构模型:
typedef_my_union
unsignedintall;
/*sizeof(my_union.my_struct)必须与sizeof(my_union.all)相等*/
struct
...
}my_struct;
}my_union;
----------EXAMPLE1--------
嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。
采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。
例如,32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
存放内容
0x4000
0x78
0x4001
0x56
0x4002
0x34
0x4003
0x12
而在Big-endian模式CPU内存中的存放方式则为:
联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性就可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。
测试代码:
intisLittleEndian()
union_dword
{
intall;
struct_bytes
charbyte0;
/*对little来说是最低位,而对big则是最高位*/
charpad[3];
}bytes;
}dword;
dword.all=0x87654321;
/*all的功能就是给dword四个字节赋值*/
return(0x21==dword.bytes.byte0);
/*查看第一个字节*/
分析:
如果你的处理器调用函数isLittleEndian返回1,那么说明你的处理器为littleendian,否则为bigendian.注意,如果在littleendian处理器上,byte0和pad按内存从低到高的存放顺序:
LOW->
byte0pad[0]pad[1]pad[2]->
HIGH;
0x87654321按内存从低到高的存放顺序:
0x21
0x43
0x65
0x87,可见byte0对应到0x21。
所以通过判断dword中第一个字节dword.bytes.byte0是否与0x21相等就可以看出是否是littleendian。
----------EXAMPLE2--------
typedefunion_student
unsignedintall;
/*all可以同时清理或设置info中的所有内容*/
struct
unsignedint
pad:
7;
/*7bit*/
unsignedint
used:
1;
/*高位*/
personal_id:
class_id:
3;
subject_id:
6;
score:
/*低位*/
}info;
}student;
#defineMAX_STUDENT_NUM100
students;
student_database[MAX_STUDENT_NUM]={0};
int
add_to_database(unsignedintdata);
find_from_database(unsignedintpersonal_id);
delete_from_database(unsignedintpersonal_id);
void
print_database(void);
void
print_student(unsignedintdata);
intadd_to_database(unsignedintdata)
studentstu;
inti;
for(i=0;
MAX_STUDENT_NUM;
i++)
stu.all=student_database[i];
if(!
stu.info.used)
stu.all
=data;
stu.info.used
=1;
student_database[i]=stu.all;
return1;
return0;
}
intfind_from_database(unsignedintpersonal_id)
if(stu.info.used&
&
stu.info.personal_id==personal_id)
{
returnstu.all;
return-1;
voidprint_student(unsignedintdata)
stu.all=data;
printf("
personalid%d,classid%d,subjectid%d,score%d\n"
stu.info.personal_id,stu.info.class_id,stu.info.subject_id,
stu.info.score);
voidprint_database(void)
if(stu.info.used)
print_student(stu.all);
intmain(intargc,char*argv[])
studentjack,jone;
jack.all
=0;
jack.info.personal_id
=102;
jack.info.class_id
=2;
/*class2*/
jack.info.subject_id
=2;
/*English*/
jack.info.score
=50;
/*fouled*/
add_to_database(jack.all);
jone.all
=0;
jone.info.personal_id
=88;
jone.info.class_id
/*calss2*/
jone.info.subject_id
/*English*/
jone.info.score
=73;
/*passed*/
add_to_database(jone.all);
jack.all=find_from_database(jack.info.personal_id);
if(jack.all<
0)
nosuchstudentwithid%d\n"
jone.info.personal_id);
else
found!
);
print_student(jack.all);
print_database();
运行结果:
personalid102,classid2,subjectid2,score50
personalid102,classid2,subjectid2,score50
personalid88,classid2,subjectid2,score73
2.union巧妙地实现多字化节数据类型之间的转化
在涉及音视频编解码算法中,经常会涉及一些数据压缩、声音解码、图象的缩放等问题。
这里通过一个例子来推荐