怎么把位域合成一个字节_C语言中字节对齐和位域
1.1基本类型默认字节对齐和改变字节对齐方式
c语言在给不同类型变量分配地址空间时,并不是总是紧邻着上一个变量的地址空间分配的,而是它所在的地址空间,必须被它的默认对齐字节数整除。例如,int类型占4字节,其默认对齐字节数为4,那么它所在的地址的低4位必须为0x0、0x4、0x8和0xc,因为这些地址才能被4整除;又如short类型,其本身占2字节,默认对齐字节数为2,则其地址的低4位必须为0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe;那么char型就不用说了,他可以分配在任意地址。我们可以通过alignas()改变类型的对齐方式,通过alignof()获取类型的默认对齐大小,这两个参数在stdalign.h头文件中。示例如下:
#include
#include
int main(int argc, char * argv[])
{
char a;
int b;
short c;
alignas(double) char d;
char e;
printf("&a [%p]\n", &a);
printf("&b [%p]\n", &b);
printf("&c [%p]\n", &c);
printf("&d [%p]\n", &d);
printf("&e [%p]\n", &e);
printf("sizeof(d) [%d]\n", sizeof(d));
printf("alignof(char) [%d]\n", alignof(char));
printf("alignof(short) [%d]\n", alignof(short));
printf("alignof(int) [%d]\n", alignof(int));
return 0;
}
结果:gcc
version 4.8.1 (GCC)
[root@192 util]# ./main
&a [0xbfed301f]
&b [0xbfed3018]
&c [0xbfed3016]
&d [0xbfed3010] //d分配了一个8字节对齐的地址空间,但并未分配8个字节的内存给d
&e [0xbfed300f]
sizeof(d) [1] //d占得空间大小未变
alignof(char) [1]
alignof(short) [2]
alignof(int) [4]
从结果中可以看到地址的分配顺序是从a到e分配的,地址从高到低。如果是从e到a分配的话,c的地址应该为0xbfed3012,因为c是short类型,只占两个字节。虽然d的对齐方式为double,但它实际占用的内存也只为1字节,只是给它分配地址方式必须和double一样,按8字节对齐方式分配,所以这里c和d只相差了6个字节。
1.2字节对齐的好处
第一个好处是加快读取速度,拿32位机来说,它一次从内存读取4字节数据,都是从能被4整除的地址开始读取的。当一个4字节int型刚好按默认字节对齐时,那么32位机一次就刚好能将该整型读走,因为它们刚好对齐。如果这个int型刚好分配在0x02-0x5地址处,则32位机必须先读走0x00-0x03地址的数据,再读走0x04-0x07数据之后将0x02-0x03和0x04-0x05地址数据拼接在一起才能得到该int型的实际数据。在这里前者快了一倍的速度。
第二个好处是可以节省内存,比如可以将所有数据紧挨在一起,但这样降低了运行效率,后面将会讲到如何使数据紧挨到一起分配。
1.3复合数据类型对齐以及改变其对齐方式
除了基本类型之外,复合类型如结构体和联合等都能改变其默认对齐方式,gcc通常默认的对齐方式为4字节。可以改变对齐方式有下边几种方法:
(1)#pragma pack(n) //编译时,使对齐方式为n。n必须是2的幂指数,且n只有在小于结构体成员的最大默认对齐字节数才生效。就是只能用来减小对齐方式。
#pragma pack() //恢复默认对齐方式
(2)#pragma pack(push, n) //保存以前设置的对齐方式,并使当前对齐方式变成n
#pragma pack(pop) //恢复到之前的对齐方式
注意:#pragma pack(push, n)等价于#pragma pack(push); #pragma pack(n)
示例:
#include
#include
int main()
{
struct fo1 { //默认4字节对齐
char a;
int b;
short c;
}foo1;
#pragma pack(2) //设置成两字节对齐
struct fo2 {
char a;
int b;
short c;
}foo2;
#pragma pack(push, 1) //保存两字节对齐状态,设置成1字节对齐
struct fo3 {
char a;
int b;
short c;
}foo3;
#pragma pack(pop) //恢复到之前的字节对齐方式,为两字节对齐
struct fo4 {
char a;
int b;
short c;
}foo4;
#pragma pack() //还原到默认4字节对齐方式
struct fo5 {
char a;
int b;
short c;
}foo5;
#pragma pack(8) //设置成8字节对齐方式
struct fo6 {
char a;
int b;
short c;
}foo6;
#pragma pack()
printf("sizeof(foo1) [%zd]\n", sizeof(foo1));
printf("&foo1.a [%p]\n", &foo1.a);
printf("&foo1.b [%p]\n", &foo1.b);
printf("&foo1.c [%p]\n", &foo1.c);
printf("\nsizeof(foo2) [%zd]\n", sizeof(foo2));
printf("&foo2.a [%p]\n", &foo2.a);
printf("&foo2.b [%p]\n", &foo2.b);
printf("&foo2.c [%p]\n", &foo2.c);
printf("\nsizeof(foo3) [%zd]\n", sizeof(foo3));
printf("&foo3.a [%p]\n", &foo3.a);
printf("&foo3.b [%p]\n", &foo3.b);
printf("&foo3.c [%p]\n", &foo3.c);
printf("\nsizeof(foo4) [%zd]\n", sizeof(foo4));
printf("&foo4.a [%p]\n", &foo4.a);
printf("&foo4.b [%p]\n", &foo4.b);
printf("&foo4.c [%p]\n", &foo4.c);
printf("\nsizeof(foo5) [%zd]\n", sizeof(foo5));
printf("&foo5.a [%p]\n", &foo5.a);
printf("&foo5.b [%p]\n", &foo5.b);
printf("&foo5.c [%p]\n", &foo5.c);
printf("\nsizeof(foo6) [%zd]\n", sizeof(foo6));
printf("&foo6.a [%p]\n", &foo6.a);
printf("&foo6.b [%p]\n", &foo6.b);
printf("&foo6.c [%p]\n", &foo6.c);
return 0;
}
结果:gcc version 4.8.1 (GCC)
[root@192 util]# ./a.out
sizeof(foo1) [12] //默认4字节对齐时占了12字节
&foo1.a [0xbfae9b84] //a占了1个字节,a与b之间有3个字节未用
&foo1.b [0xbfae9b88] //b占了4个字节,与c之间 空隙
&foo1.c [0xbfae9b8c] //c占了两个字节,为补满4字节,后有两个空字节
sizeof(foo2) [8] //默认2字节对齐
&foo2.a [0xbfae9b7c] //a占2字节
&foo2.b [0xbfae9b7e] //b占4字节
&foo2.c [0xbfae9b82] //c占2字节
sizeof(foo3) [7] //保存了2字节对齐的状态,当前1字节对齐
&foo3.a [0xbfae9b75] //a占1字节
&foo3.b [0xbfae9b76] //b占4字节
&foo3.c [0xbfae9b7a] //c占2字节
sizeof(foo4) [8] //恢复到2字节对齐
&foo4.a [0xbfae9b6c]
&foo4.b [0xbfae9b6e]
&foo4.c [0xbfae9b72]
sizeof(foo5) [12] //恢复到4字节对齐
&foo5.a [0xbfae9b60]
&foo5.b [0xbfae9b64]
&foo5.c [0xbfae9b68]
sizeof(foo6) [12] //设置成8字节对齐,未生效
&foo6.a [0xbfae9b54]
&foo6.b [0xbfae9b58]
&foo6.c [0xbfae9b5c]
从结果中可以看到,foo6并没有变成8字节对齐。原因是foo6结果中最大字节对齐的成员为int,为4字节对齐,它比8字节小,所以不能对齐成8字节。这里说明了#pragma pack()只能减小字节对齐。
(3)__attribute__((__packed__))
//取消字节优化对齐,即都是1字节对齐
__attribute__((packed)) //同上
示例:
#include
#include
int main()
{
typedef struct {
char a;
int b;
short c;
}__attribute__((__packed__)) fo1;
typedef struct {
char a;
int b;
short c;
}__attribute__((packed)) fo2;
typedef struct {
char a;
int b;
short c;
}fo3;
fo1 foo1;
printf("sizeof(foo1) [%zd]\n", sizeof(foo1));
printf("&foo1.a [%p]\n", &foo1.a);
printf("&foo1.b [%p]\n", &foo1.b);
printf("&foo1.c [%p]\n", &foo1.c);
fo2 foo2;
printf("\nsizeof(foo2) [%zd]\n", sizeof(foo2));
printf("&foo2.a [%p]\n", &foo2.a);
printf("&foo2.b [%p]\n", &foo2.b);
printf("&foo2.c [%p]\n", &foo2.c);
fo3 foo3;
printf("\nsizeof(foo3) [%zd]\n", sizeof(foo3));
printf("&foo3.a [%p]\n", &foo3.a);
printf("&foo3.b [%p]\n", &foo3.b);
printf("&foo3.c [%p]\n", &foo3.c);
return 0;
}
结果:gcc version 4.8.1 (GCC)
[root@192 util]# ./a.out
sizeof(foo1) [7]
&foo1.a [0xbf8ea529] //a占1字节
&foo1.b [0xbf8ea52a] //b占4字节
&foo1.c [0xbf8ea52e] //c占2字节
sizeof(foo2) [7]
&foo2.a [0xbf8ea522]
&foo2.b [0xbf8ea523]
&foo2.c [0xbf8ea527]
sizeof(foo3) [12]
&foo3.a [0xbf8ea514]
&foo3.b [0xbf8ea518]
&foo3.c [0xbf8ea51c]
(4)__attribute__((aligned(n)))
//该参数并不会改变每个成员的默认对齐方式,而是将变量占用的空间大小指定为n的整数倍,空字节用于填充,且变量的首地址也必须被n整除。只有当n大于结构体成员的最大对齐字节数时才有效,n为2的幂指数。当然,该参数对于基本变量也有效。
__attribute__((aligned)) //自动选择最有益的方式对齐,可以提高拷贝操作的效率。此处意义不明?
__attribute__((__aligned__)) //同上
示例:
#include
#include
int main()
{
typedef struct {
char a;
char b;
char c;
}__attribute__((aligned)) fo1;
typedef struct {
char a;
int b;
short c;
}__attribute__((__aligned__)) fo2;
typedef struct {
char a;
int b;
short c;
}__attribute__((aligned(512))) fo3;
typedef struct {
char a;
int b;
short c;
}__attribute__((aligned(1))) fo4;
fo1 foo1;
printf("sizeof(foo1) [%zd]\n", sizeof(foo1));
printf("&foo1.a [%p]\n", &foo1.a);
printf("&foo1.b [%p]\n", &foo1.b);
printf("&foo1.c [%p]\n", &foo1.c);
fo2 foo2;
printf("\nsizeof(foo2) [%zd]\n", sizeof(foo2));
printf("&foo2.a [%p]\n", &foo2.a);
printf("&foo2.b [%p]\n", &foo2.b);
printf("&foo2.c [%p]\n", &foo2.c);
fo3 foo3;
printf("\nsizeof(foo3) [%zd]\n", sizeof(foo3));
printf("&foo3.a [%p]\n", &foo3.a);
printf("&foo3.b [%p]\n", &foo3.b);
printf("&foo3.c [%p]\n", &foo3.c);
fo4 foo4;
printf("\nsizeof(foo4) [%zd]\n", sizeof(foo4));
printf("&foo4.a [%p]\n", &foo4.a);
printf("&foo4.b [%p]\n", &foo4.b);
printf("&foo4.c [%p]\n", &foo4.c);
char a ;
char b __attribute__((__aligned__));
char c;
printf("\n&a [%p]\n", &a);
printf("&b [%p]\n", &b);
printf("&c [%p]\n", &c);
return 0;
}
结果:gcc version 4.8.1 (GCC)
[root@192 util]# ./a.out
sizeof(foo1) [16]
&foo1.a [0xbfa16df0]
&foo1.b [0xbfa16df1]
&foo1.c [0xbfa16df2]
sizeof(foo2) [16]
&foo2.a [0xbfa16de0]
&foo2.b [0xbfa16de4]
&foo2.c [0xbfa16de8]
sizeof(foo3) [512]
&foo3.a [0xbfa16a00]
&foo3.b [0xbfa16a04]
&foo3.c [0xbfa16a08]
sizeof(foo4) [12]
&foo4.a [0xbfa169f4]
&foo4.b [0xbfa169f8]
&foo4.c [0xbfa169fc]
&a [0xbfa169f3]
&b [0xbfa169f0] // __attribute__((__aligned__))
&c [0xbfa169ef]
从示例中可以看出foo2和foo1都自动优化成了4字节对齐,foo3变成512字节对齐,foo4没有生效。因为foo4的对齐小于成员的最大对齐字节数。关于变量abc的地址关系,表示没看懂,没搞懂这个优化是什么意思。
1.4结构体中变量的排列方式对字节对齐的影响
在结构体中不同类型成员的位置关系也会影响结构体所占空间的大小,合理的为结构体成员排序可以起到节省内存的作用。我们可以看一下下边的例子:
#include
#include
int main()
{
typedef struct {
char a;
int b;
short c;
} fo1;
typedef struct {
int a;
short b;
char c;
} fo2;
typedef struct { //使用pad填充结构体中的空隙
char a;
char pad[3];
int b;
short c;
char pad1[2];
} fo3;
typedef struct { //使用pad填充结构体中的空隙
int a;
short b;
char c;
char pad[1];
} fo4;
fo1 foo1;
printf("sizeof(foo1) [%zd]\n", sizeof(foo1));
printf("&foo1.a [%p]\n", &foo1.a);
printf("&foo1.b [%p]\n", &foo1.b);
printf("&foo1.c [%p]\n", &foo1.c);
fo2 foo2;
printf("\nsizeof(foo2) [%zd]\n", sizeof(foo2));
printf("&foo2.a [%p]\n", &foo2.a);
printf("&foo2.b [%p]\n", &foo2.b);
printf("&foo2.c [%p]\n", &foo2.c);
fo3 foo3;
printf("\nsizeof(foo3) [%zd]\n", sizeof(foo3));
printf("&foo3.a [%p]\n", &foo3.a);
printf("&foo3.pad[0] [%p]\n", &foo3.pad[0]);
printf("&foo3.pad[1] [%p]\n", &foo3.pad[1]);
printf("&foo3.pad[2] [%p]\n", &foo3.pad[2]);
printf("&foo3.b [%p]\n", &foo3.b);
printf("&foo3.c [%p]\n", &foo3.c);
printf("&foo3.pad1[0] [%p]\n", &foo3.pad1[0]);
printf("&foo3.pad1[1] [%p]\n", &foo3.pad1[1]);
fo4 foo4;
printf("\nsizeof(foo4) [%zd]\n", sizeof(foo4));
printf("&foo4.a [%p]\n", &foo4.a);
printf("&foo4.b [%p]\n", &foo4.b);
printf("&foo4.c [%p]\n", &foo4.c);
printf("&foo4.pad[0] [%p]\n", &foo4.pad[0]);
return 0;
}
结果:gcc version 4.8.1 (GCC)
[root@192 util]# ./a.out
sizeof(foo1) [12]
&foo1.a [0xbf8db714]
&foo1.b [0xbf8db718]
&foo1.c [0xbf8db71c]
sizeof(foo2) [8]
&foo2.a [0xbf8db70c]
&foo2.b [0xbf8db710]
&foo2.c [0xbf8db712]
sizeof(foo3) [12]
&foo3.a [0xbf8db700]
&foo3.pad[0] [0xbf8db701] //pad刚好填充了a与b中的空隙
&foo3.pad[1] [0xbf8db702]
&foo3.pad[2] [0xbf8db703]
&foo3.b [0xbf8db704]
&foo3.c [0xbf8db708]
&foo3.pad1[0] [0xbf8db70a] //pad1刚好填充了c后边的空隙
&foo3.pad1[1] [0xbf8db70b]
sizeof(foo4) [8]
&foo4.a [0xbf8db6f8]
&foo4.b [0xbf8db6fc]
&foo4.c [0xbf8db6fe]
&foo4.pad[0] [0xbf8db6ff]
从上边的例子可以看出,由于默认地址4字节的对齐属性,导致了foo1和foo2中都有未使用到的空间。foo3和foo4使用pad填充并证实了这些空隙。还可以看出不同成员变量的位置不一样,占用的空间不一样,这里也许可以得到这个结论,将结构体成员中对齐字节数高的变量从大到小依次排列,可以减小结构体所占用的内存。当然,这种方法应该只对部分情况奏效。
1.5结构体成员中有结构体时字节对齐对其的影响
如果改变了某个结构体的默认对齐方式,且该结构体成员中有结构体变量,那么这种对齐方式的改变不会影响结构体成员。可以通过以下示例得到证明:
#include
#include
void hex_printf(unsigned char * buff, int len)
{
for (int i = 0; i < len; i++) {
printf("%02hhx ", buff[i]);
if ((i+1) % 16 == 0) {
printf("\n");
}
}
printf("\n");
}
int main()
{
typedef struct {
char a;
int b;
short c;
} fo;
typedef struct {
int a;
short b;
char c;
fo d;
} fo1;
#pragma pack(1) //一字节对齐
typedef struct {
int a;
short b;
char c;
fo d;
} fo2;
#pragma pack()
fo1 foo1;
memset(&foo1, 0, sizeof(foo1));
foo1.a = 1;
foo1.b = 2;
foo1.c = 3;
foo1.d.a = 4;
foo1.d.b = 5;
foo1.d.c = 6;
hex_printf(&foo1, sizeof(foo1));
printf("sizeof(foo1) [%zd]\n", sizeof(foo1));
fo2 foo2;
memset(&foo2, 0, sizeof(foo2));
foo2.a = 1;
foo2.b = 2;
foo2.c = 3;
foo2.d.a = 4;
foo2.d.b = 5;
foo2.d.c = 6;
printf("\n");
hex_printf(&foo2, sizeof(foo2));
printf("sizeof(foo2) [%zd]\n", sizeof(foo2));
return 0;
}
结果:gcc version 4.8.1 (GCC)
[root@192 util]# ./a.out
01 00 00 00 02 00 03 00 04 00 00 00 05 00 00 00
06 00 00 00
sizeof(foo1) [20] //foo1中abc占了8字节,d占了12字节
01 00 00 00 02 00 03 04 00 00 00 05 00 00 00 06
00 00 00
sizeof(foo2) [19] //foo1中的abc安一字节对齐了,而d未受影响
从结果可以看出,foo1的实际大小是foo1除了结构体成员以外的其他成员对齐以后占空间的大小加上结构体成员自身对齐后占空间的大小。且改变结构体对齐方式,并不会影响其结构体成员。
1.6位域
结构体的最小成员还可以是bit位,可以将单个整型的不同位组合成多个变量成员。我们可以参考下边的示例:
#include
#include
void hex_printf(unsigned char * buff, int len)
{
for (int i = 0; i < len; i++) {
printf("%02hhx ", buff[i]);
if ((i+1) % 16 == 0) {
printf("\n");
}
}
printf("\n");
}
int main()
{
typedef struct bitbox {
unsigned int a:1;
unsigned int :3; //占位,表示紧邻的3个位不使用
unsigned int b:4;
unsigned int c:1;
unsigned int :0; //将unsigned int的剩下所有位都填满
unsigned int d:1; //从下一个unsigned int中分配1位给d
}fo1;
fo1 foo1;
memset(&foo1, 0, sizeof(foo1));
foo1.a = 1;
foo1.b = 3;
foo1.c = 1;
foo1.d = 1;
hex_printf(&foo1, sizeof(foo1));
printf("sizeof(foo1) [%zd]\n", sizeof(foo1));
return 0;
}
结果:gcc version 4.8.1 (GCC)
[root@192 util]# ./a.out
31 01 00 00 01 00 00 00
sizeof(foo1) [8]
从打印结果来看这是一个小端机,即低字节存储在低地址中。第一个字节是0x31,其中存储了a、b的值和3个空位。从二进制00110001来看,b和a是倒序排列的,即不是按结构体中的顺序排列在一字节上的。从第五个字节可以看到,d存储在第二个unsigned int中,说明了unsigned int :0确实将第一个unsigned int中未使用的位全部填充了。
怎么把位域合成一个字节_C语言中字节对齐和位域相关推荐
- 位域 内存 字节序_C语言中的位域、字节序、比特序、大小端(转)
1.比特序 / 位序 / bit numbering / 我们知道一个字节有8位,也就是8个比特位.从第0位到第7位共8位.比特序就是用来描述比特位在字节中的存放顺序的.通过阅读网页的内容,关于比特序 ...
- 语言余一个负数_C语言学习日记(8)——整数溢出
小时候喜欢玩电子计算器,觉得很神奇,想要算什么数,立刻就能算出来.当时看着计算器的液晶屏幕就想,如果算一个很大的数,超过了屏幕上面数字的位数,会怎么样呢?试了以后结果是INF,那就是无穷大了.虽然当时 ...
- c语言把bit数据合为一个字节,C语言中怎么定义bit型数据
一.通过sbit或者bit定义 sbit映射到IO口(P1^1这种IO口的"位") bit在RAM中的可位寻址空间中,一般用作程序判断的标志位. 认为它们一个对外(sbit),一个 ...
- C语言中结构体的位域(bit-fields)
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可.例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位.正是基于这种考虑,C语言又提供了一种叫做位域 ...
- c语言字符串加减_C语言中指针的介绍
C语言中指针的介绍 指针是C语言中广泛使用的一种数据类型. 运用指针编程是C语言最主要的风格之一.利用指针变量可以表示各种数据结构:能很方便地使用数组和字符串: 并能象汇编语言一样处理内存地址,从而编 ...
- file是c语言自带的数据类型吗_C语言中基本的数据类型包括
1 / 22 1 .外部变量对程序中地任一函数而言, 它们是 ( ) A 存在地: B 可直接存取地: C 不可见地: D 不可直接存取地 2 .下列说法中错误地是 () A 变量地定义可以放在所有函 ...
- c语言指针官方解释_C语言中的指针解释了–它们并不像您想象的那么难
c语言指针官方解释 Pointers are arguably the most difficult feature of C to understand. But, they are one of ...
- c语言设置输出字符大小_C语言中常用的几个头文件及库函数
点击上方"C语言中文社区",选择"设为星标★" 技术干货第一时间送达! 来源:https://www.jb51.net/article/124594.htm 这 ...
- python文件定位函数_C语言中文件定位函数总结
C语言中文件定位函数主要是:fseek, ftell, fsetpos, fgetpos. 先来讲前两个函数,这是最基本的定位函数: fseek函数:能把文件指针移动到文件任何位置,其原型是:int ...
最新文章
- etcd数据库备份与还原
- JDK动态代理实现简单AOP--转
- gwt-2.8.2下载_GWT EJB3 Maven JBoss 5.1集成教程
- 采用Bert进行中文分词
- C++ 二维数组作为形参传递使用实例
- SpringBoot自学笔记《黑马程序员》
- 金蝶k3服务器维护,金蝶k3如何远程客服服务器
- 百家cms v4.1.4漏洞
- 珍藏版 | 20道XGBoost面试题
- ipv6无线传感器网络服务器,IPv6传感器网络的应用技术和功能实现分析
- 计算机如何通过手机连接网络,主编教您手机怎么通过usb连接电脑上网
- 不定积分问题:1/x^3+1的不定积分求法
- 论文详解EnlightenGAN: Deep Light Enhancement Without Paired Supervision
- 适合公司用的电子邮箱哪家好?企业邮箱最全功能介绍~
- android 编译libjpeg-turbo
- word中使用mathtype编辑公式并添加序号
- 百度信息流介绍,没有比这更详细的啦
- 苹果拍照怎么显示地点和时间_手机拍照自带功能,照片上能添加时间和地点?一键按下搞定...
- 2021年过氧化工艺模拟试题及过氧化工艺证考试
- TSP-粒子群算法求解
热门文章
- 两大顶级AI算法一起开源!Nature、Science齐发Alphafold2相关重磅,双厨狂喜~
- 靠拿奖学金完成学业后,博士生为女友放弃年薪30万工作选择留校任教
- 计算机技能需求新排名:Python 仅排第 3,第 1 你可能猜不到哦
- 笔记精选(返回点赞总数和挑选笔记数量)
- 静态Web服务器-多任务版
- C语言字符串一道比较难的题!_只愿与一人十指紧扣_新浪博客
- 动手推导Self-Attention
- 年底了,没啥好送的,送个1T移动硬盘吧~
- 【2019雅礼集训】【CF 960G】【第一类斯特林数】【NTT多项式】permutation
- 九、将cs文件快速的转换成可执行文件和响应文件(配置编译开关的文件)