關於結構體中各個變量在內存中所佔空間

首先,我們來看一下下面這段代碼。

#include <stdio.h>

int main() 
{
	struct A{
		char a;
		int b;
		double c;
	}part1;
	
	struct B{
		char a;
		double b;
		int c;
	}part_one;
	
	printf("%d %d", sizeof(part1), sizeof(part_one));
	
	return 0;
}

我們不妨假設這兩個結構體所佔內存字節數爲其包含的各個變量所佔字節數的代數和,那麼,運行結果應該是1 + 4 + 8 和 1 + 8 + 4,也就是13 13。但是,最終我們得到的運行結果卻是這樣子的:
運行結果

這就不得不提到計算機中存在的一種叫做內存對齊的機制了。

  • 什麼是內存對齊呢?
    內存對齊的規則是這樣子的(引用自百度百科):

每個特定平臺上的編譯器都有自己的默認“對齊係數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。

  • 對齊規則:
  1. 數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,以後每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
  2. 結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
  3. 結合1、2可推斷:當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。
    注:#pragma pack(n)是用於設定變量以n字節對齊方式對齊(即變量起始位置在內存空間中的偏移量是對齊係數的倍數)。

另外,不得不提到的是,我們的計算機的對齊策略一般是這樣子的:
一、起始位置爲成員數據類型所佔內存的整數倍,若不足則不足部分用數據將內存填充爲該數據類型的整數倍。
二、結構體所佔總內存爲其成員變量中所佔空間最大數據類型的整數倍。

本人使用的是x86平臺,默認對齊係數爲8,因此,綜上所述,我們可以對最開始那段代碼進行分析。

#include <stdio.h>
#pragma pack(8)

int main() 
{
	struct A{
		char a;	//char型,1 < 8,按1對齊;起始offset爲0 
		int b;	//int型,4 < 8,按4對齊;因0被佔用,起始offset爲4 
		double c;//double型,8 = 8,按8對齊;因0被佔用,起始offset爲8 
	}part1;
	
	struct B{
		char a;	//char型,1 < 8,按1對齊;起始offset爲0
		double b;//double型,8 = 8,按8對齊;因0被佔用,起始offset爲8
		int c;	//int型,4 < 8,按4對齊;因0,4,8均已被佔用,起始offset爲16(4是被自動填充,爲了保證數據存放) 
	}part_one;
	//對這段代碼進行分析之後,我們不難發現其中存在一些"空洞",對於這些空洞,程序會自動補零
	//於是,其最終結果是16和24也就不難理解了
	
	printf("%d %d", sizeof(part1), sizeof(part_one));
	
	return 0;
}

那麼,這裏又引起了一個新的問題,內存對齊機制是怎麼來的呢?
在計算機中通常會讓CPU從內存中一次讀取若干個字節的數據,而不是一次只讀取一個字節的數據,這樣的好處是提高了計算機的效率,然而壞處也顯而易見。
假設CPU一次從內存中讀取四個字節的數據,而現在內存中存在一個char型的數據和一個int型的數據,如果內存不對齊,當CPU第一次跨越四個字節尋址找到了一個char型的數據,而此時CPU的指向到了int型的中間區域,導致這個int型變量未找到,然後CPU會返回去再次尋找,直到找到該int型變量。這樣不但沒能提高效率,反而增加了CPU的負擔。因此我們通常會在第一個char型變量後邊填充一部分數據來保證每次尋址時地址都是該數據的整數倍,這樣就避免了上述“錯誤”的發生,也就是所謂的內存對齊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章