1. 程式人生 > >記憶體對齊與位域

記憶體對齊與位域

記憶體對齊:

在討論之前我們先看一個栗子:

#include<iostream>

using namespae std;

int main()
{
    struct A
    {
      int a;
      char b;
      char c;
    };
    struct B
    {
        char b;
        int a;
        char c;
    }
    cout<<sizeof(A)<<endl;
    system("pause");
    return 0;
}

從上面程式碼中,我們首先計算結構體A的大小,int型別佔4位元組,char型別佔1位元組,那麼結構體的大小是不是4+1+1=6呢?
我們再來看結構體B與結構體A的成員是相同的,但是所佔記憶體大小是不是相同呢?我想你們心中已經有答案了,不管你的答案是什麼,我們現在來驗證驗證?

結果

嗯?結構體A的大小不是6麼?而且結構體A和結構體B大小不應該相同嗎?

那麼帶著上面這些疑問,我們來探究為什麼結構體A的大小為8而不是6,為什麼結構體B與結構體A的大小不同。

其實這一切都是記憶體對齊搞的鬼!!

那麼,問題來了:1.什麼是記憶體對齊?
2.為什麼要記憶體對齊?
3.怎樣記憶體對齊?

1.什麼是記憶體對齊?

百度詞條上面的解釋:編譯器為程式中的每個“資料單元“安排在適當的位置上。

2.為什麼要記憶體對齊?

  1. 平臺原因:各個硬體平臺對儲存空間的處理上有很大的不同。一些平臺對某些特定型別的資料只能從某些特定地址開始存取。————- 比如,有些架構的CPU在訪問 一個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證位元組對齊。

  2. 效能原因:記憶體對齊可以提高存取效率。————- 比如,有些平臺每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那麼一個讀週期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該32bit資料。

假若有一片記憶體是這樣儲存的:
cpu讀取方式

我們知道一位元組佔用8位,那麼32位的硬體平臺一次最多讀取4位元組,如果讀取上圖中的int型別資料,假若沒有記憶體對齊,cpu會首先讀取前4位元組,將多餘的資料(char型別變數)剔除,然後再次讀取4位元組,將多餘的資料剔除,然後再次將兩次保留的資料進行拼合。為了產生對比,我們用記憶體對齊的方式作比較,如圖:

記憶體對齊

顯而易見,記憶體對齊的int資料可以一次性讀取,提高了讀取資料的效率!

3.怎樣記憶體對齊(記憶體對齊的規則)?

說完了記憶體對齊的原因,現在我們就看看記憶體對齊的規則:
1. 結構體變數的首地址是有效對齊值的的整數倍。
2. 結構體第一個成員的偏移量是0,以後每個成員相對於結構體首地址的偏移量都是該成員大小有效對齊值中較小那個的整數倍
3. 結構體的大小為有效對齊值的整數倍,如有需要,編譯器會在最後一個成員之後填充位元組。
4. 結構體內型別相同的連續元素將在連續空間內,和陣列相同。

有效對齊值:是 ==#pragma pack指定值==和結構體中最長資料型別長度 中較小的那個。有效對齊值也叫對齊單位。

有以上4條規則來回頭看我們開始的例子:

從例子可以得知,我們只是調換了結構體中成員變數的順序。結構體中最長的資料型別為int,4位元組,vs2015預設#pragma pack(8),所以有效對齊值為4位元組,我們可以畫出如下示例圖:

例子

因此,我們可以知道為什麼結構體A的大小為8,而且和結構體B的大小不同。

注意:不同編譯器支援的有效對齊值不同,比如GCC只支援1,2,4對齊。

結構體巢狀記憶體對齊規則:

在一個結構體中遇到另一個結構體型別變數,則進入到另一結構體中對於其成員變數以相同規律進行對齊,最後結構體總大小等於兩個結構體中所有成員變數中最大對齊數的整數倍.

#pragma pack(4)
struct A{
  int a;
  double b;
};

struct B{
  A s;          //有效對齊值為4
  char c;
};

結構體B的大小為16。

位域:

還是三個問題:
1. 什麼是位域?
2. 為什麼用位域?
3. 位域怎麼用?

1.什麼是位域?

位域是指資訊在儲存時,並不需要佔用一個完整的位元組, 而只需佔幾個或一個二進位制位

2.為什麼使用位域?

為了節省儲存空間,並使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。所謂“位域”是把一個位元組中的二進位劃分為幾 個不同的區域, 並說明每個區域的位數。每個域有一個域名,允許在程式中按域名進行操作。 這樣就可以把幾個不同的物件用一個位元組的二進位制位域來表示。

3.位域怎樣用?
1. 位域的定義和位於變數的說明(位域的定義與說明與結構體的定義相仿)

struct 位域結構名
{
位域列表
};

位域列表的形式為:型別說明符 位域名:位域長度

struct bs{
    int a:8;
    int b:2;
    int c:6;
}data;

data佔兩個位元組,其中a佔有8位,b佔有2位,c佔有6位。

位域的對齊

1) 如果相鄰位域欄位的型別相同,且其位寬之和小於型別的sizeof大小,則後面的欄位將緊鄰前一個欄位儲存,直到不能容納為止;
2) 如果相鄰位域欄位的型別相同,但其位寬之和大於型別的sizeof大小,則後面的欄位將從新的儲存單元開始,其偏移量為其型別大小的整數倍;
3) 如果相鄰的位域欄位的型別不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式(不同位域欄位存放在不同的位域型別位元組中),Dev-C++和GCC都採取壓縮方式;

系統會先為結構體成員按照對齊方式分配空間和填塞(padding),然後對變數進行位域操作。

在使用位域還需注意:

1) 一個位域必須儲存在同一個位元組中,不能跨兩個位元組。
2) 由於位域不允許跨兩個位元組,因此位域的長度不能大於一個位元組的長度。
3) 位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。
4) 一般宣告位域時用==signed int====unsigned int==,而不用int。因為若使用的是int,則到底是解釋為有符號數還是無符號數由編譯器決定。

struct bs{
  unsigned a:4;
  unsigned b:5; //從下一位元組存放
  unsigned c:1
  unsigned  :3; //無位域名,無法使用
};

我們可以看到位域大大減少了記憶體的浪費。當然,在位域的運用中,我們還需注意大小端對位域的影響問題。因為位域中的成員在記憶體中是從左向右分配還是從右向左分配由處理器的大小端決定。

相關推薦

記憶體

記憶體對齊: 在討論之前我們先看一個栗子: #include<iostream> using namespae std; int main() { struct A { int a; char b;

記憶體以及

首先我們大家先思考一個問題,為什麼編譯器會有記憶體對齊這種東西呢? 原因有二: 一.平臺原因: 某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。 二.效能原因: 如果訪問的是未對齊的記憶體,處理器需要做兩次記憶體訪問;如果記憶體對齊,則處理器只需要做一次

sizeof(結構體)和記憶體以及

Win32平臺下的微軟C編譯器的對齊策略:1) 結構體變數的首地址能夠被其最寬基本型別成員的大小所整除;備註:編譯器在給結構體開闢空間時,首先找到結構體中最寬的基本資料型別,然後尋找記憶體地址能被該基本資料型別所整除的位置,作為結構體的首地址。將這個最寬的基本資料型別的大小作

初識-----記憶體

記憶體對齊:        首先,先看一段程式碼:               我們可以看出上面那個結構體的長度為24,按照正常的計算方法,它的長度應該為14,但是現在卻是24,這是什麼原因呢?        原來在程式執行的過程中為了便於cpu快速訪問,同時有效地節

C++字節

oat sizeof int long 結構 code 操作符 targe 一個 一、字節對齊:   說明:為了提高 CPU 的存儲速度,編譯器會對 struct 和 union的存儲進行優化,即進行字節對齊。   1. 指定對齊參數值:通過#pragma pac

結構體記憶體段,列舉+聯合

1.結構體記憶體對齊 結構體記憶體對齊規則 1.第一個成員在與結構體變數偏移量為0的地址處 2.其他成員變數要對齊到對齊數的整數倍的地址處。對齊數=編譯器預設的一個對齊數與該成員大小的 較小值   vs中預設的值為8,linux中的預設值為4 3.結構體總大小為最

1.結構體型別建立 2.結構體初始化 3.結構體記憶體 4.段,段計算機大小。 5.列舉 6.聯合

結構體型別的建立 1.結構體的宣告 結構是一些值的集合,這些值稱為成員變數。每個結構體的成員可以是不同型別的變數。 struct Student { char name[20];//名字 short age;//年齡 char sex[5

c語言記憶體#pragma pack(n)

一、什麼是記憶體對齊,為什麼要記憶體對齊  現在計算機記憶體空間都是按照byte位元組劃分的,理論上講對任何型別的變數的訪問可以從任何地址開始,但實際情況是在訪問特定型別變數的時候經常在特定的記憶體地址上訪問,這就需要各種資料型別按照一定的規則在空間上排列,而不是一個接一個

記憶體記憶體分配原則

首先講一個概念—-記憶體對齊 一種提高記憶體訪問速度的策略,cpu在訪問未對其的記憶體需要經過兩次記憶體訪問,而經過記憶體對齊一次就可以了。(?) 打個比方就是:作業系統在訪問記憶體時,每次讀取一定的長度(這個長度是系統預設的對其係數),程式中你也可以自己設

記憶體 struct型資料的記憶體佈局

當在C中定義了一個結構型別時,它的大小是否等於各欄位(field)大小之和?編譯器將如何在記憶體中放置這些欄位?ANSI   C對結構體的記憶體佈局有什麼要求?而我們的程式又能否依賴這種佈局?這些問題或許對不少朋友來說還有點模糊,那麼本文就試著探究它們背後的祕密。   首先,

初學者對於結構體記憶體的理解

1、問題提出: 剛開始學習結構體的時候,我們通常認為結構體 struct number_1 { int a; int b; int c; }st1; 佔用空間為sizeof(st1.a)+sizeof(st1.b)+sizeof(s

Delphi中的記憶體 Packed關鍵字

Record的資料各個位元組都是對齊的,資料格式比較完整,所以這種格式相對packed佔用的記憶體比較大,但是因為格式比較整齊,所以電腦讀取這個型別的資料的時候速度比較快。而Packed Record對資料進行了壓縮,節省了記憶體空間,當然他的速度也變的慢了。  type         //    Decl

[轉]、大小端、記憶體

 宣告:由於本文的程式碼會受到計算機環境的影響,故在此說明本篇博文中的程式的執行環境。          1、Microsoft Windows 7 Ultimate Edition Service Pack 1 (64bit  6.1.7601)          2、M

sizeof:(含)結構體記憶體,壓縮儲存

注:沒有額外宣告的結果均是在VC++環境中測試得到的結果。 1. sizeof 給出其運算元儲存位元組大小。 cout << " sizeof: " << endl; cout <&

LLVM的呼叫協議記憶體

在設計一門語言與其他語言互動的API與ABI(Application Binary Interface,二進位制介面)時,呼叫協議和記憶體對齊是兩個無從迴避的問題。 本文將討論如何在LLVM上生成正確的記憶體對齊和呼叫協議的程式碼。 在這裡為了方便和標準起見,假定應用LLVM的語言的Extending

C語言之struct大小、首地址記憶體—由結構體成員地址得到結構體首地址

被問到如下問題:給定一個結構體中某個變數地址,可否得到結構體變數的地址? 答案是可以,但是對不同的場合有不同的結果;這與微處理器平臺、編譯器的處理不可分割。 首先,對於處理器,大尾端、小尾端的因素必須考慮; 其次: 一、 ANSIC標準中並沒有規定,相鄰宣告的變數在記憶體中一定要相鄰。 為了程式的高效性,

函式棧結構記憶體

函式棧 下圖是x86-64的函式棧的結構, 函式P呼叫函式Q的過程,Q正在執行。 這裡僅對兩個地方解釋,其他的很容易理解,就不細說了 其中返回地址用來實現函式的返回。當Q要返回到P時,就要呼叫此地址獲取返回位置。 引數構造區是對呼叫函式P傳遞的引數的

結構體、聯合體和斷的記憶體問題

記憶體對齊的原因: 1.平臺原因    不是所有硬體平臺都可以訪問任意地址上的任意資料;    某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。 2.效能原因   資料結構(尤其是

記憶體問題(結構體,聯合體,段)

結構體 typedef struct A { char c1; char c2; int i; }A; typedef struct B { char c1; int i; char c2; }B; type

記憶體分配記憶體全面探討

引言 作業系統的記憶體分配問題與記憶體對齊問題對於低層程式設計來說是非常重要的,對記憶體分配的理解直接影響到程式碼質量、正確率、效率以及程式設計師對記憶體使用情況、溢位、洩露等的判斷力。而記憶體對齊是常常被忽略的問題,理解記憶體對齊原理及方法則有助於幫助程