数据的对齐
cpu在读写内存的时候,如果所给的地址是机器字长的整数倍,则操作效率会比较高,这可以称之为地址的对齐。
在一般的32bits机器上,地址对齐的界线默认是4的整数倍。
struct my_struct{
char ch1; //1字节
char ch2; //1字节
int n; //4字节
char ch3; 1字节
};//整个结构体在32bits系统占12个字节
对结构体类型采用sizeof操作符,得到的是结构体占用的内存字节数,包括所有的空闲字节,显然,这个值并不一定等于它的所有成员的大小之和。
#pragma pack(1) //将地址对齐界线改为1的整数
struct my_struct{
char ch1; //1字节
char ch2; //1字节
int n; //4字节
char ch3; 1字节
};//整个结构体占7个字节
#pragma pack() //将地址对齐界线改回原来的值
对齐规则参考
该博文也给出了#pragma pack(show) 和#pragma pack(num)的说明以及在VS中的设置;默认num=8.
这内存宝贵的应用场合,比如嵌入式系统,一般要考虑对齐,一般原则,可以是从小到大排,把多个小字节变量放一起,并且跟对齐的大小对齐。
一般来说,
- 结构体的对齐规则是先按数据类型自身进行对齐,这里需要比较#pragma pack(num)中num的数值和数据类型的大小,取小的作为基准;
- 然后再按整个结构体进行对齐,对齐值必须是2的幂,比如1,2, 4, 8, 16。准确来讲,结构的总大小是其成员中最大类型的sizeof(该类型)整数倍。
- 如果一个类型按n字节对齐,那么该类型的变量起始地址必须是n的倍数。比如int按四字节对齐,那么int类型的变量起始地址一定是4的倍数,比如0x0012ff60,0x0012ff48等。
查看系统是多少位的
file /sbin/init
/sbin/init: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=7a4c688d009fc1f06ffc692f5f42ab09e68582b2, stripped
file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3bf9ca54ea5e261943509c2e47bc814bb1248921, stripped
uname -a
Linux ubuntu 3.13.0-79-generic #123-Ubuntu SMP Fri Feb 19 14:27:58 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
cat /proc/version
Linux version 3.13.0-79-generic (buildd@lcy01-24) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) ) #123-Ubuntu SMP Fri Feb 19 14:27:58 UTC 2016
cat /etc/issue
Ubuntu 15.04
l
测试数据所占字节
因为在数据对齐的时候需要考虑是在什么系统下,各数据类型所占字节大小,纠正网上一些有问题的解答,比如long是只占4bits的。
cout << "char: " << sizeof(char) << endl;
cout << "short: " << sizeof(short) << endl;
cout << "int: " << sizeof(int) << endl;
cout << "long: " << sizeof(long) << endl;
cout << "unsigned long: " << sizeof(unsigned long) << endl;
cout << "long long: " << sizeof(long long) << endl;
//在vc6.0下不支持 long long 数据类型。
cout << "double: " << sizeof(double) << endl;
cout << "char*: " << sizeof(char*) << endl;
32bit windows xp (VC6.0 IDE 32bits) 测试:
char: 1
short: 2
int: 4
long: 4
unsigned long: 4
double: 8
char*: 4
64bit windows 10 (Dev-C++ 5.11 64bits的)测试:
Window系统下的MinGW,总是编译为32位代码。因为MinGW只支持32位代码。
char: 1
short: 2
int: 4
long: 4 // 不知道为什指针又是8字节的!!!
unsigned long: 4
long long: 8
double: 8
char*: 8 //指针所占内存大小有区别,每字节8位,跟总线搭配
64bit ubuntu15.04 (g++ 4.8.4)测试:
char: 1
short: 2
int: 4
long: 8 // 8 !!
unsigned long: 8
long long: 8
double: 8
char*: 8 //指针所占内存大小有区别,每字节8位,跟总线搭配
地址对齐的概念:
- 由于硬件设计的特点,CPU在读写内存时,如果所给的地址是机器字长的整数倍,则操作效率会比较高。
- 有的CPU甚至不支持读写地址不对齐的内存单元。
- 为了提高程序运行的效率,编译器会尽量避免一个变量(包括结构体成员)的存储空间跨越地址对齐的界线。
-
实验(64bits 环境)
#include <iostream> using namespace std; typedef struct{ }test_null; // test 1 typedef struct{ double d1; //8 char c1; //1 short s1; //补1+2 char ch2; //1 int i1; //补3+4+补4 => 24 }testdouble; // test 2 typedef struct{ int d1; //4 char c1; //1 short s1; //fill 1 + 2 char ch2; //1 int i1; //fill 3 + 4 => 16 }testint; // test 3 #pragma pack(2) typedef struct{ int d1; //4 char c1; //1 short s1; //fill 1 +2 char ch2; //1 int i1; //fill 1 + 4 => 14 }testint2; class BU{ int number; //4 union UBffer{ char buffer[13];//13 int number; //4 }ubuf; //->13 void foo(){} //0 typedef char*(*f)(void*); //0 enum{hdd,ssd,blueray}disk; //fill 1 + 4 int* a; //8 => 30 }bu; // test 4 #pragma(4) #pragma pack(1) // typedef struct { char ch1; //1 char ch2; //1 int n; //4 char ch3; //1 }stch3_1; // -> 7 class clt_1{ char ch1; //1 int a; //4 short b; //2 char c; //1 stch3_1 st1;//7 => 15 }st_inside; // test 5 #pragma pack(4) //
输出控制:
-
64bits win10(GCC 4.9.2)测试结果:
test null struct: 1 sizeof(bu): 30 sizeof(testdouble):24 sizeof(testint):16 sizeof(testint2):14 st_inside: 15, inside stch3_1: 7
-
64bits ubuntu (gcc version 4.8.4 ) 测试结果:
test null struct: 1 sizeof(bu): 30 sizeof(testdouble):24 sizeof(testint):16 sizeof(testint2):14 st_inside: 15, inside stch3_1: 7
注意:
- 空的结构体在linux系统中并不是0,它和windows 一样,均是占一个字节;
- long类型在32bits 是占4个字节,64bits 编译器中是8字节;
- 在32bits 系统中指针是4字节,64bits系统占8字节,由于总线物理跟指针的空间是一样大的:4*8 -> 32 ; 8*8 -> 64 .