c/c++深刻篇以內存分配與內存對齊的探討



不明白內存分配和指針的能夠看看,其實這本是咱們老師留的一個操做系統科技小論文做業,不知道寫什麼,乾脆把之前收藏的經典C內存分配的文章整理並修改了一下。       此文章有2個用處,1:這是個小論文,格式完整,你們能夠複製回去交做業;2:這是整理的經典C內存分配小教程(也加了些我本身的觀點),不明白內存分配的能夠看看。c++

還有很重要的一個問題:      這篇文章引用的不少內容我也不知道到底是出自誰手,知道做者是誰的麻煩告訴下,我好謝謝他。(記得都是csdn裏面找的)程序員

tag: 操做系統 論文 內存分配 內存對齊 c語言內存分配 免費論文下載.doc編程

正文:數組

關於程序設計的內存分配問題安全

freec數據結構

(遼寧工程技術大學 軟件學院 SJ07-3 遼寧 葫蘆島 125000函數

[  ] 性能

在大多數低層程序設計中,因爲內存分配與內存對齊問題所帶來的bug所佔比重很是大。本文對內存分配中的分配空間類型、做用、方法、適用範圍、優缺點以及內存對齊問題中的對齊緣由、對齊規則等進行了詳細的說明,並結合大量c語言代碼進行闡述與分析。大數據

[關鍵詞]ui

內存分配;堆棧原理;內存對齊;

 

1 引言

操做系統的內存分配問題與內存對齊問題對於低層程序設計來講是很是重要的,對內存分配的理解直接影響到代碼質量、正確率、效率以及程序員對內存使用狀況、溢出、泄露等的判斷力。而內存對齊是經常被忽略的問題,理解內存對齊原理及方法則有助於幫助程序員判斷訪問非法內存。

 

2 程序的內存分配問題

 

1、通常C/C++程序佔用的內存主要分爲5

    1、棧區(stack):相似於堆棧,由程序自動建立、自動釋放。函數參數、局部變量以及返回點等信息都存於其中。

    2、堆區(heap): 使用自由,不需預先肯定大小。多數狀況下須要由程序員手動申請、釋放。如不釋放,程序結束後由操做系統垃圾回收機制收回。

    3、全局區/靜態區(static):全局變量和靜態變量的存儲是區域。程序結束後由系統釋放。

    4、文字常量區:常量字符串就是放在這裏的。 程序結束後由系統釋放。

5、程序代碼區:既可執行代碼。

 

例:

#include <stdio.h>

int quanju;/*全局變量,全局區/靜態區(static*/

void fun(int f_jubu); /*程序代碼區*/

int main(void)/**/

{

       int m_jubu;/*棧區(stack*/

       static int m_jingtai;/*靜態變量,全局區/靜態區(static*/

       char *m_zifum,*m_zifuc = "hello";/*指針自己位於棧。指向字符串"hello",位於文字常量區*/

       void (*pfun)(int); /*棧區(stack*/

       pfun=&fun;

       m_zifum = (char *)malloc(sizeof(char)*10);/*指針內容指向分配空間,位於堆區(heap*/

       pfun(1);

       printf("&quanju   : %x/n",&quanju);

       printf("&m_jubu   : %x/n",&m_jubu);

       printf("&m_jingtai: %x/n",&m_jingtai);

       printf("m_zifuc   : %x/n",m_zifuc);

       printf("&m_zifuc  : %x/n",&m_zifuc);

       printf("m_zifum   : %x/n",m_zifum);

       printf("&m_zifum  : %x/n",&m_zifum);

       printf("pfun      : %x/n",pfun);

       printf("&pfun     : %x/n",&pfun);

       getch();

       return 0;

}

void fun(int f_jubu)

{

       static int f_jingtai;

       printf("&f_jingtai: %x/n",&f_jingtai);

       printf("&f_jubu   : %x/n",&f_jubu);/*棧區(stack,可是與主函數中m_jubu位於不一樣的棧*/

}

 

輸出結果:

&f_jingtai: 404020

&f_jubu   : 22ff40

&quanju   : 404070

&m_jubu   : 22ff74

&m_jingtai: 404010

m_zifuc   : 403000

&m_zifuc  : 22ff6c

m_zifum   : 3d24e0

&m_zifum  : 22ff70

pfun      : 4013af

&pfun     : 22ff68

 

分析:

堆區:

       m_zifum   : 3d24e0

代碼區:

       pfun      : 4013af

局區/靜態區(static:

       m_zifuc   : 403000 

       &m_jingtai: 404010

       &f_jingtai: 404020

       &quanju   : 404070

棧區:

       &f_jubu   : 22ff40 fun函數棧區

       &pfun     : 22ff68 主函數棧區 

       &m_zifuc  : 22ff6c

       &m_zifum  : 22ff70

       &m_jubu   : 22ff74

 

2、堆和棧

1申請方式

stack:

由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間

heap:

須要程序員手動申請,並指明大小,在c中,有malloc函數完成

p1 = (char *)malloc(10);

C++中用new運算符

p2 = (char *)malloc(10);

可是注意p1p2自己是在棧中的。 

 

2 申請後系統的響應

棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。

堆:大多數操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的free函數才能正確的釋放本內存空間。另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。

 

3申請大小的限制

棧:在Windows,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。

堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。

 

4申請效率的比較:

棧由系統自動分配,速度較快。但程序員是沒法控制的。

堆是由程序員手動分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便.

另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。可是速度快,也最靈活。

 

5堆和棧中的存儲內容

棧: 在函數調用時,第一個進棧的是函數調用語句的下一條可執行語句的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。 當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是函數中的下一條指令,程序由該點繼續運行。

堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。

 

6存取效率的比較

char s1[] = "aaaaaaaaaaaaaaa";

char *s2 = "bbbbbbbbbbbbbbbbb";

aaaaaaaaaaa是在運行時刻賦值的;

bbbbbbbbbbb是在編譯時就肯定的;

可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。

好比:

#include

void main()

{

char a = 1;

char c[] = "1234567890";

char *p ="1234567890";

a = c[1];

a = p[1];

return;

}

對應的彙編代碼

: a = c[1];

00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

0040106A 88 4D FC mov byte ptr [ebp-4],cl

: a = p[1];

0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

00401070 8A 42 01 mov al,byte ptr [edx+1]

00401073 88 45 FC mov byte ptr [ebp-4],al

第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據edx讀取字符,顯然慢了一些。

 

2 內存對齊問題

 

1、內存對齊的緣由

大部分的參考資料都是如是說的:

1、平臺緣由(移植緣由):不是全部的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,不然拋出硬件異常。

2、性能緣由:數據結構(尤爲是棧)應該儘量地在天然邊界上對齊。緣由在於,爲了訪問未對齊的內存,處理器須要做兩次內存訪問;而對齊的內存訪問僅須要一次訪問。

 

2、對齊規則

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

規則:

1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset0的地方,之後每一個數據成員的對齊按照#pragma pack指定的數值和這個數據成員

自身長度中,比較小的那個進行。

2、結構(或聯合)的總體對齊規則:在數據成員完成各自對齊以後,結構(或聯合)自己也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。

3、結合12可推斷:當#pragma packn值等於或超過全部數據成員長度的時候,這個n值的大小將不產生任何效果。

 

3、試驗

下面咱們經過一系列例子的詳細說明來證實這個規則

編譯器:GCC 3.4.2VC6.0

平臺:Windows XP

 

典型的struct對齊

struct定義:

#pragma pack(n) /* n = 1, 2, 4, 8, 16 */

struct test_t {

 int a;

 char b;

 short c;

 char d;

};

#pragma pack(n)

首先確認在試驗平臺上的各個類型的size,經驗證兩個編譯器的輸出均爲:

sizeof(char) = 1

sizeof(short) = 2

sizeof(int) = 4

 

試驗過程以下:經過#pragma pack(n)改變「對齊係數」,而後察看sizeof(struct test_t)的值。

 

11字節對齊(#pragma pack(1))

輸出結果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]

分析過程:

1) 成員數據對齊

#pragma pack(1)

struct test_t {

 int a;  /* 長度4 > 1 1對齊;起始offset=0 0%1=0;存放位置區間[0,3] */

 char b;  /* 長度1 = 1 1對齊;起始offset=4 4%1=0;存放位置區間[4] */

 short c; /* 長度2 > 1 1對齊;起始offset=5 5%1=0;存放位置區間[5,6] */

 char d;  /* 長度1 = 1 1對齊;起始offset=7 7%1=0;存放位置區間[7] */

};

#pragma pack()

成員總大小=8

 

2) 總體對齊

總體對齊係數 = min((max(int,short,char), 1) = 1

總體大小(size)=$(成員總大小) $(總體對齊係數) 圓整 = 8 /* 8%1=0 */ [1]

 

22字節對齊(#pragma pack(2))

輸出結果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]

分析過程:

1) 成員數據對齊

#pragma pack(2)

struct test_t {

 int a;  /* 長度4 > 2 2對齊;起始offset=0 0%2=0;存放位置區間[0,3] */

 char b;  /* 長度1 < 2 1對齊;起始offset=4 4%1=0;存放位置區間[4] */

 short c; /* 長度2 = 2 2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

 char d;  /* 長度1 < 2 1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9

2) 總體對齊

總體對齊係數 = min((max(int,short,char), 2) = 2

總體大小(size)=$(成員總大小) $(總體對齊係數) 圓整 = 10 /* 10%2=0 */

 

34字節對齊(#pragma pack(4))

輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]

分析過程:

1) 成員數據對齊

#pragma pack(4)

struct test_t {

 int a;  /* 長度4 = 4 4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */

 char b;  /* 長度1 < 4 1對齊;起始offset=4 4%1=0;存放位置區間[4] */

 short c; /* 長度2 < 4 2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

 char d;  /* 長度1 < 4 1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9

 

2) 總體對齊

總體對齊係數 = min((max(int,short,char), 4) = 4

總體大小(size)=$(成員總大小) $(總體對齊係數) 圓整 = 12 /* 12%4=0 */

 

48字節對齊(#pragma pack(8))

輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]

分析過程:

1) 成員數據對齊

#pragma pack(8)

struct test_t {

 int a;  /* 長度4 < 8 4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */

 char b;  /* 長度1 < 8 1對齊;起始offset=4 4%1=0;存放位置區間[4] */

 short c; /* 長度2 < 8 2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

 char d;  /* 長度1 < 8 1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9

2) 總體對齊

總體對齊係數 = min((max(int,short,char), 8) = 4

總體大小(size)=$(成員總大小) $(總體對齊係數) 圓整 = 12 /* 12%4=0 */

 

516字節對齊(#pragma pack(16))

輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]

分析過程:

1) 成員數據對齊

#pragma pack(16)

struct test_t {

 int a;  /* 長度4 < 16 4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */

 char b;  /* 長度1 < 16 1對齊;起始offset=4 4%1=0;存放位置區間[4] */

 short c; /* 長度2 < 16 2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

 char d;  /* 長度1 < 16 1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9

 

2) 總體對齊

總體對齊係數 = min((max(int,short,char), 16) = 4

總體大小(size)=$(成員總大小) $(總體對齊係數) 圓整 = 12 /* 12%4=0 */

8字節和16字節對齊試驗證實了「規則」的第3點:「當#pragma packn值等於或超過全部數據成員長度的時候,這個n值的大小將不產生任何效果」。

 

4結束語

內存分配與內存對齊是個很複雜的東西,不但與具體實現密切相關,並且在不一樣的操做系統,編譯器或硬件平臺上規則也不盡相同,雖然目前大多數系統/語言都具備自動管理、分配並隱藏低層操做的功能,使得應用程序編寫大爲簡單,程序員不在須要考慮詳細的內存分配問題。可是,在系統或驅動級以致於高實時,高保密性的程序開發過程當中,程序內存分配問題仍舊是保證整個程序穩定,安全,高效的基礎。

 

 

[參考文獻及技術支持]

[1] Brian.W.Kerighan <the C programming language> 2004.1

[2] W.richard stevens <unix環境高級編程> 2006.10

[3]csdn開發社區 c/c++版塊 提供技術支持

[4]50M深藍程序設計討論組 提供技術支持 

[1]

什麼是「圓整」?

舉例說明:如上面的8字節對齊中的「總體對齊」,總體大小=9 4 圓整 = 12

圓整的過程:從9開始每次加一,看是否能被4整除,這裏91011均不能被4整除,到12時能夠,則圓整結束。

原文連接:http://blog.csdn.net/cuibo1123/article/details/2547442

相關文章
相關標籤/搜索