概述
1 内存对齐
以下是一些对齐值的概念
- 数据类型自身的对齐值:基本数据类型的自身对齐值即自然对齐值。
- 指定对齐值:#pragma pack (value)时的指定对齐值value。
- 结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值。
- 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
1.1 编译器的对其策略
Win32平台下的微软C编译器(cl.exe for 80×86)的对齐策略如下
- 结构体变量的首地址能够被其最宽基本类型成员的有效对齐值所整除
- 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员有效对齐值的整数倍.
- 结构体的总大小为结构体最宽基本类型成员有效对齐值的整数倍
注 :数组按其元素类型对齐
struct MyStruct {
double dda1;
char dda;
int type
};
sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:
struct MyStruct {
char dda;
double dda1;
int type
};
sizeof(MyStruc)为1+7+8+4+4=24
1.2 编译器的pack指令
它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。
- 使用伪指令
#pragma pack (n)
,编译器将按照n个字节对齐。 - 使用伪指令
#pragma pack ()
,取消自定义字节对齐方式。
2 “空结构体”
“空结构体”(不含数据成员)的大小不为0,而是1。
试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢?于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。
如下:
struct S { };
sizeof(S);
sizeof( S ) 为1
3 结构体中的 #define
只是为了增加可读性 和放在结构体外没有区别
4 位域
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。位段成员必须声明为int、unsigned int或signed int类型。
4.1 定义
位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度 。
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
struct bs {
int a:8;
int b:2;
int c:6;
}data;
data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。
1.如果一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*从下一单元开始存放*/
unsigned c:4
}
这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
2.位域的长度不能大于数据类型本身的长度,比如int类型不能超过32位二进位。
3.位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k {
int a:1
int :2 /*该2位不能使用*/
int b:3
int c:2
};
- 结构体中可以同时有位域和普通成员
4.2 用法
其一般形式为: 位域变量名.位域名 。位域允许用各种格式输出。
struct bs {
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a = 1;
bit.b = 7; //位域的赋值不能超过该域所能表示的最大值,如b只有3位,能表示的最大数为7,若赋为8,就会出错
bit.c = 15;
printf("%d,%d,%d/n",bit.a,bit.b,bit.c);
pbit = &bit;
pbit->a = 0;
pbit->b &= 3;
pbit->c |= 1;
printf("%d,%d,%d/n",pbit->a,pbit->b,pbit->c);
上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。
4.3 对齐
我们再来看看下面两个结构体定义:
struct foo2 {
char a : 2;
char b : 3;
char c : 1;
};
struct foo3 {
char a : 2;
char b : 3;
char c : 7;
};
打印一下这两个结构体的大小,得到的结果是
sizeof(struct foo2) = 1
sizeof(struct foo3) = 2
foo2这个结构体中所有的成员都是char型的,而且三个位域占用的总空间为6 bit < 8 bit(1 byte),这时编译器会将这三个成员’捆绑’在一起做对齐。
foo3三个成员类型也都是char型,但是三个成员位域所占空间之和为9 bit > 8 bit(1 byte),这里位域是不能跨越两个成员基本类型空间的,这时编译器将a和b两个成员’捆绑’按照char做对齐,而c单独拿出来以char类型做对齐。
struct foo4 {
char a : 2;
char b : 3;
int c : 1;
};
打印一下sizeof(struct foo4)发现结果为8
4.4 应用
c语言里规定的位域的方式进行比特位操作,可以对某一位进行操作;一般情况下,比如普通的存储数据的区域,位域没有任何意义。通常,位域可以用来进行对一些要进行比特位操作的场合,比如标志寄存器的标志位清零,或者配置寄存器功能设置等。例如:
struct MYSTRUCT {
int menber0 :width;
int menber1 :width;
int menber2 :width;
int menber3 :width;
int menber4 :width;
int menber5 :width;
int menber6 :width;
int menber7 :width;
}name1;
当你要对一类这样的寄存器位单独操作的时候,把寄存器变量的地址映射为这样的一个结构体,例如
#define REGISTER0 (*(volatile struct MYSTRUCT *) 0x48000000)
这样就把寄存器实地址映射为一个MYSTRUCT的结构体。
调用某一比特位,以这样的格式例如,把第一比特位置高:
REGISTER0->menber0 = 1;
不能用
REGISTER0.menber0 = 1;
这种格式是错的。
5 形如&((struct s *) 0)->d
相当于把0地址转换为指向element结构的指针,然后取其成员地址,结果为0 + 成员d的偏移地址。该语句看似访问0地址,实际只是在编译阶段执行的,因此不会引起异常。
可以任意常数的地址:写成 &((struct s *) 1000)->d,即把1000地址转换为指向element结构的指针,然后取其成员地址。结果为1000 + 成员d的偏移地址。
最后
以上就是烂漫大侠为你收集整理的struct1 内存对齐2 “空结构体”3 结构体中的 #define4 位域5 形如&((struct s *) 0)->d的全部内容,希望文章能够帮你解决struct1 内存对齐2 “空结构体”3 结构体中的 #define4 位域5 形如&((struct s *) 0)->d所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复