C++每日一问:内存对齐

C++为什么要内存对齐

1)性能原因

内存对齐可以提高存取效率(例如,有些平台每次读都是从偶地址开始,如果一个int型存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并且要对两次读出的结果的高低字节进行拼凑才能得到这32bit的数据)

2)平台原因

各个硬件平台对存储空间的处理有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取,例如,有些架构的CPU在访问一个没有对齐的变量时会发生错误,那么这时候编程必须保证字节对齐

正文

简要说明:结构体成员按照定义时的顺序依次存储在连续的内存空间,但是结构体的大小并不是简单的把所有成员大小相加,而是遵循一定的规则,需要考虑到系统在存储结构体变量时的地址对齐问题。

一、没有成员的结构体占用的空间是多少个字节?

答案是:1个字节。 这就是实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类或空结构体(C++中结构体也可看为类)隐含的加一个字节,这样空类或空结构体在实例化后在内存得到了独一无二的地址,所以空类所占的内存大小是1个字节。

二、首先介绍一个相关的概念——偏移量

  struct stru 
     {
    
      
                int a;  //start address is 0
               char b;  //start address is 4
               int c;  //start address is 8
     };

偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。比如上面的结构体,第一个成员a的偏移量为0。第二个成员b的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为4;第三个成员c的偏移量是第二个成员的偏移量应该是加上第二个成员的大小(4+1)。

三、在实际中,存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则:

(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。

例子1:

struct stru1  
{
    
      
     int a;  //start address is 0
     char b;  //start address is 4
     int c;  //start address is 8
};

PS:用sizeof求该结构体的大小,发现值为12。int占4个字节,char占1个字节,结果应该是9个字节才对啊,为什么呢?这个例子中前两个成员的偏移量都满足要求,但第三个成员的偏移量为5,并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上3个空字节,使得第三个成员的偏移量变成8。结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为12,满足要求。

例子2:

struct stru2  
{
    
      
      int i;  //start address is 0
      short m;  //start address is 4
};

PS:成员i的偏移量为0;成员m的偏移量为4,都不需要调整。但计算出来的大小为6,显然不是成员m大小的整数倍。因此,编译器会在成员m后面补上2个字节,使得结构体的大小变成8从而满足第二个要求。

例子3、4:

 struct stru3  
{
    
       
       char i;  //start address is 0 
       int m;   //start address is 4
       char n;  //start address is 8
};  
struct stru4  
{
    
      
       char i;  //start address is 0
       char n;  //start address is 1
       int m;  //start address is 4
 }; 

虽然结构体stru3和stru4中成员都一样,但sizeof(struct stru3)的值为12而sizeof(struct stru4)的值为8。

由此可见,结构体类型需要考虑到字节对齐的情况,不同的顺序会影响结构体的大小。

四、对于嵌套的结构体,需要将其展开。对结构体求sizeof时,上述两种原则变为:
(1)展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
(2)结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体。

例子1:

struct stru5  
{
    
      
	short i;  
	struct   
	{
    
      
		char c;  
		int j;  
	} tt;   
	int k;  
};

结构体stru5的成员tt.c的偏移量应该是4,而不是2。整个结构体大小应该是16。

例子2:

struct stru6  
{
    
      
      char i;  
      struct   
      {
    
      
           char c;  
           int j;  
      } tt;   
      char a;  
      char b;  
      char d;  
      char e;  
      int f;  
};

结构体tt单独计算占用空间为8,而stru6的sizeof则是20,不是8的整数倍,这说明在计算sizeof(stru6)时,将嵌套的结构体ss展开了,这样stu6中最大的成员为tt.j,占用4个字节,20为4的整数倍。如果将tt当做一个整体,结果应该是24了。

五:另一个特殊的例子是结构体中包含数组,其sizeof应当和处理嵌套结构体一样,将其展开,如下例子:

struct array  
{
    
      
    float f;  
    char p;  
    int  arr[3];  
};

其值为20。float占4个字节,到char p时偏移量为4,p占一个字节,到int arr[3]时偏移量为5,扩展为int的整数倍,而非int arr[3]的整数倍,这样偏移量变为8,而不是12。结果是8+12=20,是最大成员float或int的大小的整数倍。

测试环境:vc++6.0
测试代码:

//#ifndef __cplusplus

//#endif


#include <iostream>
#include "stdio.h"
#include <stdlib.h>
using namespace std;

struct stru_empty
{
    
    

};
struct stru1
{
    
    
	int a; //start address is 0
	char b; //start address is 4
	int c; //start address is 8
};
struct stru2
{
    
    
	int i; //start address is 0
	short m; //start address is 4
};
struct stru3
{
    
    
	char i; //start address is 0
	int m; //start address is 4
	char n; //start address is 8
};
struct stru4
{
    
    
	char i; //start address is 0
	char n; //start address is 1
	int m; //start address is 4
};

struct stru5
{
    
    
	short i;
	struct
	{
    
    
		char c;
		int j;
	} ss;
	int k;
};
struct stru6
{
    
    
	char i;
	struct
	{
    
    
		char c;
		int j;
	} tt;
	char a;
	char b;
	char d;
	char e;
	int f;
};
struct stru7
{
    
    
	char i;
	struct
	{
    
    
		char c;
		//int j;
	} tt;
	char a;
	char b;
	char d;
	char e;
	int f;
};
struct array
{
    
    
	float f;
	char p;
	int arr[3];
};
int main()
{
    
    
	struct stru6 st6;
	struct stru7 st7;
	struct array ar;
	printf("sizof(char)=%d \n",sizeof(char));
	printf("sizof(int)=%d \n",sizeof(int));
	printf("sizof(short int)=%d \n",sizeof(short int));
	printf("sizof(long int)=%d \n",sizeof(long int));
	printf("sizof(long)=%d \n",sizeof(long));
	printf("sizof(float)=%d \n\n",sizeof(float));

	printf("sizof(stru_empty)=%d \n",sizeof(stru_empty));
	printf("sizof(stru1)=%d \n\n",sizeof(stru1));
	printf("sizof(stru2)=%d \n\n",sizeof(stru2));
	printf("sizof(stru3)=%d \n\n",sizeof(stru3));
	printf("sizof(stru4)=%d \n\n",sizeof(stru4));
	printf("sizof(stru5)=%d \n\n",sizeof(stru5));
	printf("sizof(stru6)=%d \n",sizeof(stru6));
	printf("sizof(stru6.tt)=%d \n",sizeof(st6.tt));
	printf("the address of stru6.i=%d \n",&st6.i);
	printf("the address of stru6.a=%d \n\n",&st6.a);

	printf("sizof(stru7)=%d \n",sizeof(stru7));
	printf("sizof(stru7)=%d \n",sizeof(st7.tt));
	printf("the address of stru7.i=%d \n",&st7.i);
	printf("the address of stru7.a=%d \n\n",&st7.a);

	printf("sizof(ar)=%d \n",sizeof(ar));
	printf("sizof(ar.f)=%d \n",sizeof(ar.f));
	printf("sizof(ar.arr)=%d \n",sizeof(ar.arr));
	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_37251750/article/details/120956746
今日推荐