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 <stdio.h>
-
#include <stdalign.h>
-
-
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 <stdio.h>
-
#include <string.h>
-
-
-
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 <stdio.h>
-
#include <string.h>
-
-
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 <stdio.h>
-
#include <string.h>
-
-
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 <stdio.h>
-
#include <string.h>
-
-
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 <stdio.h>
-
#include <string.h>
-
-
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 <stdio.h>
-
#include <string.h>
-
-
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中未使用的位全部填充了。
参考资料:
http://blog.csdn.net/markl22222/article/details/38051483
http://www.cnblogs.com/ransn/p/5081198.html
http://blog.csdn.net/mylinx/article/details/7007309
http://blog.csdn.net/jnu_kinke/article/details/7420185
阅读(3058) | 评论(0) | 转发(0) |