詳解結構體 類等記憶體位元組對齊

2021-07-11 04:24:16 字數 3891 閱讀 3117

先說個題外話:早些年我學c程式設計時,寫過一段解釋硬碟mbr分割槽表的**,對著磁碟編輯器怎麼看,怎麼對,可一執行,結果就錯了。當時除錯也不太會,又根本沒聽過結構體對齊這一說,所以,問題解決不了,好幾天都十分糾結。後來萬般無奈請教乙個朋友,才獲悉可能是結構體對齊的事,一查、一改,果真如此。

問題是解決了,可網上的資料多數只提到記憶體對齊是如何做的,卻鮮有提及為什麼這樣做(即使提,也相當簡單)。筆者是個超級健忘者,很難機械式的記住這些破規則,於是仔細想了想,總算明白了原因,這樣,這些對齊的規則也就不會再輕易忘記了。     

不光結構體存在記憶體對齊一說,類(物件)也如此,甚至於所有變數在記憶體中的儲存也有對齊一說(只是這些對程式設計師是透明的,不需要關心)。實際上,這種對齊是為了在空間與複雜度上達到平衡的一種技術手段,簡單的講,是為了在可接受的空間浪費的前提下,盡可能的提高對相同運算過程的最少(快)處理。先舉個例子:

假設機器字長是32位的(即4位元組,下面示例均按此字長),也就是說處理任何記憶體中的資料,其實都是按32位的單位進行的。現在有2個變數:    

char a; 

int b; 

假設這2個變數是從記憶體位址0開始分配的,如果不考慮對齊,應該是這樣儲存的(見下圖,以intel上的little endian為例,為了形象,每16個位元組分做一行,後同):

因為計算機的字長是4位元組的,所以在處理變數a與b時的過程可能大致為:

a:將0x00-0x03共32位讀入暫存器,再通過左移24位再右移24位運算得到a的值(或與0x000000ff做與運算)

b:將0x00-0x03這32位讀入暫存器,通過位運算得到低24位的值;再將0x04-0x07這32位讀入暫存器,通過位運算得到高8位的值;再與最先得到的24位做位運算,才可得到整個32位的值。

上面敘述可知,對a的處理是最簡處理,可對b的處理,本身是個32位數,處理的時候卻得折成2部分,之後再合併,效率上就有些低了。

想解決這個問題,就需要付出幾個位元組浪費的代價,改為下圖的分配方式:

按上面的分配方式,a的處理過程不變;b卻簡單得多了:只需將0x04-0x07這32位讀入暫存器就ok了。

我們可以具體談結構體或類成員的對齊了:

結構體在編譯成機器**後,其實就沒有本身的集合概念了,而類,實際上是個加強版的結構體,類的物件在例項化時,記憶體中申請的就是一些變數的空間集合(類似於結構體,同時也不包含函式指標)。這些集合中的每個變數,在使用中,都需要涉及上述的加工原則,自然也就需要在效率與空間之間做出權衡。

為了便捷加工連續多個相同型別原始變數,同時簡化原始變數定址,再彙總上述最少處理原則,通常可以將原始變數的長度做為針對此變數的分配單位,比如記憶體可用64個單元,如果某原始變數長度為8位元組,即使機器字長為4位元組,分配的時候也以8位元組對齊(看似io次數是相同的),這樣,定址、分配時,均可以按每8位元組為單位進行,簡化了操作,也可以更高效。

系統預設的對齊規則,追求的至少兩點:1、變數的最高效加工 2、達到目的1的最少空間 

舉個例子,乙個結構體如下:

//by www.datahf.net zhangyu

typedef struct t

;

假設定義了乙個結構體變數c,在記憶體中分配到了0x00的位置,顯然:

對於成員c.c  無論如何,也是一次暫存器讀入,所以先佔乙個位元組。

對於成員c.d  是個64位的變數,如果緊跟著c.c儲存,則讀入暫存器至少需要3次,為了實現最少的2次讀入,至少需要以4位元組對齊;同時對於8位元組的原始變數,為了在定址單位上統一,則需要按8位元組對齊,所以,應該分配到0x08-0xf的位置。

對於成員c.e  是個32位的變數,自然只需滿足分配起始為整數個32位即可,所以分配至0x10-0x13。

對於成員c.f  是個16位的變數,直接分配在0x14-0x16上,這樣,反正只需一次讀入暫存器後加工,邊界也與16位對齊。

對於成員c.g  是個8位的變數,本身也得一次讀入暫存器後加工,同時對於1個位元組的變數,儲存在任何位元組開始都是對齊,所以,分配到0x17的位置。

對於成員c.h  是個16位的變數,為了保證與16位邊界對齊,所以,分配到0x18-0x1a的位置。

分配圖如下(還不正確,耐心讀下去):

結構體c的占用空間到h結束就可以了嗎?我們找個示例:如果定義乙個結構體陣列 ca[2],按變數分配的原則,這2個結構體應該是在記憶體中連續儲存的,分配應該如下圖:

分析一下上圖,明顯可知,ca[1]的很多成員都不再對齊了,究其原因,是結構體的開始邊界不對齊。

那結構體的開始偏移滿足什麼條件才可以使其成員全部對齊呢。想一想就明白了:很簡單,保證結構體長度是原始成員最長分配的整數倍即可。

上述結構體應該按最長的.d成員對齊,即與8位元組對齊,這樣正確的分配圖如下:

當然結構體t的長度:sizeof(t)==0x20;

再舉個例子,看看在預設對齊規則下,各結構體成員的對齊規則:

//by www.datahf.net zhangyu

typedef struct a

; //整個結構體,最長的成員為4個位元組,需要總長度與4位元組對齊,所以, sizeof(a)==12

typedef struct b

; //整個結構體的最大分配成員為8位元組,所以結構體後面加5位元組填充,被到48位元組。故:

//sizeof(b)==48;

具體的分配圖如下:

上述全部測試**如下:

//by www.datahf.net zhangyu

#include "stdio.h"

typedef struct a

; typedef struct b

; typedef struct c

; typedef struct d

; int main()

執行時的記憶體情況如下圖:

先介紹四個概念:

1)資料型別自身的對齊值:基本資料型別的自身對齊值,等於sizeof(基本資料型別)。

2)指定對齊值:#pragma pack (value)時的指定對齊值value。

3)結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。

4)資料成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中較小的那個值。

有效對齊值n是最終用來決定資料存放位址方式的值,最重要。有效對齊n,就是表示「對齊在n上」,也就是說該資料的"存放起始位址%n=0".而資料結構中的資料變數都是按定義的先後順序來排放的。第乙個資料變數的起始位址就是 資料結構的起始位址。結構體的成員變數要對齊排放,結構體本身也要根據自身的有效對齊值圓整(就是結構體成員變數占用總長度需要是對結構體有效對齊值的整 數倍)

#pragma pack (value)來告訴編譯器,使用我們指定的對齊值來取代預設的。

如#pragma pack (1)  /*指定按2位元組對齊*/

#pragma pack () /*取消指定對齊,恢復預設對齊*/

詳解結構體 類等記憶體位元組對齊

先說個題外話 早些年我學c程式設計時,寫過一段解釋硬碟mbr分割槽表的 對著磁碟編輯器怎麼看,怎麼對,可一執行,結果就錯了。當時除錯也不太會,又根本沒聽過結構體對齊這一說,所以,問題解決不了,好幾天都十分糾結。後來萬般無奈請教乙個朋友,才獲悉可能是結構體對齊的事,一查 一改,果真如此。問題是解決了,...

記憶體中結構體位元組對齊

記憶體的對齊問題。柑橘還是沒能理解好。轉一篇文章放著看看。一 位元組對齊作用和原因 對齊的作用和原因 各個硬體平台對儲存空間的處理上有很大的不同。一些平台對某些特定型別的資料只能從某些特定位址開始訪問。比如有些架構的cpu在訪問乙個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證...

結構體位元組對齊

include pragma pack 2 struct t.pragma pack int main int argc,char argv 最後輸出的結果為 8。這個表示是按照2位元組來對齊資料,首先分配2位元組給成員變數i,分配完成後,還剩一位元組,zj add補0 沒法容納成員變數d,此時會再...