1.malloc怎么分配空間 malloc與new的關系
看完下面的2再回答這個問題。
2. linux對內存的結構描述
a) /proc/${pid}/ 存放進程運行時候所有的信息。程序一結束,該目錄就刪掉了。
b) 任何一個程序的內存空間其實分成4個基本部分。
i. 代碼區
ii. 全局棧區
iii. 堆
iv. 局部棧
小實驗: 運行一個只包含while(1);的程序,然后另起一個終端,cd /proc下面的對應進程的pid目錄,cat maps,查看到運行進程的內存空間分配情況。
進程查看: ps aue
c) 理解程序的變量與內存空間的關系
小實驗:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int add(int a, int b)
{
return a+b;
}
int a1 = 1;
static int a2 = 2;
const int a3 = 3;
main()
{
int b1 = 4;
static b2 = 5;
const b3 = 6;
int *p1 = malloc(4);
printf("a1:%p\n", &a1);
printf("a2:%p\n", &a2);
printf("a3:%p\n", &a3);
printf("b1:%p\n", &b1);
printf("b2:%p\n", &b2);
printf("b3:%p\n", &b3);
printf("p1:%p\n", p1);
printf("main:%p\n", main);
printf("add:%p\n", add);
printf("%d\n", getpid());
while(1);
}
把打印結果與/proc下對應目錄中的maps文件比較。
(代碼區一般是ox8048000開頭的區域。 )
可以看到 a3全局常量在代碼區(字面值神馬的也是放在代碼區)。 b3局部常量放在局部棧區。
a1, a2, b2 則是放在全局棧區。
main, add 在代碼區。
b1, b3在局部棧區。
p1 在堆
小實驗:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
int a1 = 10;
int a2 = 20;
int a3 = 30;
int *p1 = malloc(4);
int *p2 = malloc(4);
int *p3 = malloc(4);
printf("%p\n", &a1);
printf("%p\n", &a2);
printf("%p\n", &a3);
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p3);
printf("%p\n", &a1);
printf("%p\n", &a1);
printf("%p\n", &a1);
printf("%d\n", getpid());
while(1);
}
運行結果如下:

可以看到,a1, a2, a3的地址降序排列,相差4個字節。(棧 分配內存是直接壓到棧頂)
p1, p2, p3的地址升序排列,相差16個字節。(堆)
小結:
(1)內存分四個區。
(2)各種變量對應存放區。
(3)堆棧是一種管理內存的數據結構。查看程序的內存地址。
回到問題1.
看一個小實驗:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p1 = malloc(4);
int *p2 = malloc(4);
int *p3 = malloc(4);
*p1 = 1;
*(p1+1) = 2;
*(p1+2) = 3;
*(p1+3) = 4;
*(p1+4) = 5;
*(p1+5) = 6;
*(p1+6) = 7;
*(p1+7) = 8;
*(p1+8) = 9;
printf("%d\n", *p2);
return 0;
}
運行結果是5.
如果在程序中加一句話后:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p1 = malloc(4);
int *p2 = malloc(4);
int *p3 = malloc(4);
*p1 = 1;
*(p1+1) = 2;
*(p1+2) = 3;
*(p1+3) = 4;
*(p1+4) = 5;
*(p1+5) = 6;
*(p1+6) = 7;
*(p1+7) = 8;
*(p1+8) = 9;
free(p1); //比上面的程序多了這句話
printf("%d\n", *p2);
return 0;
}
則會發生錯誤。
p1指向int型,應該只占用4個字節。可是實際上卻占了16個字節,因為P1其實是鏈表里的一個節點,多的12個字節其實是保存的一些指向下一個節點,或者別的一些信息。我們在用*(p1+1) = 2; *(p1+2) = 3; *(p1+3) = 4;破壞這些信息的時候,不會報錯,但是在使用這個節點(free(p))時,則會報錯了。
3. 理解malloc的工作原理
malloc使用一個數據結構(鏈表)來維護分配的空間。鏈表的構成:分配的空間、上一個空間的地址、下一個空間的地址、以及本空間的信息等。對malloc分配的空間不要越界訪問,因為容易破壞后臺的鏈表維護結構,導致malloc/free/calloc/realloc不正常工作。
4. C++的new與malloc的關系
小實驗:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int *p1 = (int*)malloc(4);
int *p2 = new int;
int *p3 = (int *)malloc(4);
int *p4 = new int;
int *p5 = new int;
int *p6 = new int;
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p3);
printf("%p\n", p4);
printf("%p\n", p5);
printf("%p\n", p6);
return 0;
}
運行結果:

結論:new的實現使用的是malloc來實現的。
區別:new使用malloc后,還要初始化空間。基本類型,直接初始化成默認值。 UDT類型調用指定的構造器
推論:delete也是調用free實現。
區別:delete會調用指定的析構器,然后再調用free()。
new與new[]的區別:new只調用一個構造器初始化。new[]循環對每個區域調用構造器。
delete與delete[]的區別:delete只調用一次析構函數,而delete則把數組中的每個對象的析構函數都調用一遍。
malloc new
realloc new() //定位分配
calloc new[]
free delete
5. 函數調用棧空間分配與釋放
5.1 總結:
- 函數執行的時候有自己的臨時棧。(C++中的成員函數有對象??臻g和函數??臻g兩個空間)
- 函數的參數就在臨時棧中。如果函數傳遞實參,則用來初始化臨時參數變量。
- 通過寄存器返回值(使用返回值返回數據)
- 通過參數返回值(參數必須是指針。指針指向的區域必須事先分配)
- 如果參數返回指針,參數就是雙指針。
5.2 __stdcall, __cdecl __fastcall的問題(了解,應付面試即可)
#include <stdio.h>
int _attribute_((stdcall)) add(int *a, int *b)
{
return *a+*b;
}
int main()
{
int a1 = 20;
int b2 = 30;
int r = add(&a, &b);
printf("%d\n", r);
}
- 這三個屬性決定函數參數壓棧順序。都是從右到左。
- 決定函數棧清空的方式。是調用者清空還是被調用者清空
- 決定了函數的名字轉換方式。(編譯的時候,會把函數重新命名。)
6. far near huge指針的問題(linux中不考慮這個問題。window中屬于遺留問題。windows編程統一采用far指針)
near 16
far 32
huge 綜合
Note: C與C++明顯的不同表現在 引用, 模板, 異常以及面向對象
函數參數傳值和傳指針其實是一樣的,只是一個是把值拷貝過去,一個是把地址拷貝過去。
7.虛擬內存
小實驗:寫一個程序,定義一個整型指針,賦值為999,打印出它的地址,同時while(1)讓它一直運行著。再寫一個程序,定義一個整形指針,直接指向剛才打印出來的地址,然后打印這個指針指向的整數,為打印出999嗎? (不會,段錯誤)
問題:
為什么一個程序不能訪問另外一個程序的地址指向的空間?
理解:
- 每個程序的開始地址一般都是0x80084000。
- 由1可以看出程序中使用的地址不是物理地址,而是邏輯地址(虛擬內存)。邏輯地址僅僅是個編號,使用int 4字節整數表示。(4字節所能表示的最大整數是2的32次方=4294967296=4G)。所以每個程序提供了4G的訪問能力
問題:
邏輯地址和物理地址怎么關聯?(內存映射)
背景:
虛擬內存的提出:禁止用戶直接訪問物理存儲地址。有助于系統的穩定。
結論:
虛擬地址與物理地址在映射的時候有一個基本單位4k(16進制的1000,稱為內存頁)。
段錯誤:無效訪問。虛擬地址與物理地址沒有映射。
沒有段錯誤不一定是合法訪問。
合法訪問:比如malloc分配的空間之外的空間(malloc后就映射了)可以訪問但是訪問非法。int *p1 = malloc(4); int *(p1+12) = 233; 第二句不會報段錯誤,但是是非法訪問。
8.虛擬內存的分配
棧:編譯器自動生成代碼維護
堆:地址是否映射?映射的空間是否被管理?
- brk/sbrk內存映射函數
補充:幫助文檔:man 節 關鍵字
節:1-8 1: Linux系統(shell)指令 (ls等)
2: 系統函數 (brk等)
3: 標準C函數的幫助文檔 (fopen等)
7: 系統的編程幫助 (tcp, icmp等)
分配釋放內存
int brk(void *addr); //分配空間,釋放空間
void *sbrk(int size); //返回指定大小的空間的地址
應用:
- 使用sbrk分配內存空間 int *p = sbrk(4); //分配4字節整數
- 使用sbrk得到沒有映射的虛擬地址 int *p1 = sbrk(0); //返回沒有映射的虛擬地址的首地址,不能給*p1賦值,會出現段錯誤。因為還沒有映射。
- 使用brk分配空間
- 使用brk釋放空間
理解:
sbrk(int size)
sbrk與brk后臺系統維護一個指針。指針默認是null。
調用sbrk,判定指針是否是0(第一次調用),如果是:得到大塊空閑地址的首地址來初始化該指針。返回該指針給指針變量賦值,同時把指針指向+size的地方。如果是否:返回指針,并且將指針位置+size。
#include <stdio.h>
#include <unistd.h>
int main()
{
int *p = sbrk(0); //返回空閑地址,并修改指針為+size(這里是0,),注意這個指針不是*p,而是sbrk指向內存里的指針。
//這里是0,并且是首次調用,所以內存并沒有映射。如果括號里是4或者4的倍數,則會返回指針的同時做映射,并把sbrk的指針指向+4的位
//置以便供下一次調用的時候返回地址。并不是括號里是4就只映射4個字節的地址,而是映射一頁的內存。這是為了效率的考慮。好比吃饅頭,
//不是吃一個做一個,而是要吃了,做一屜,慢慢吃。所以 *(p+10)= 20; 是可以訪問的(p最多只能加到1023,不然仍然會段錯誤)。
//但是是非法訪問。
printf("%d\n", *p);
}
#include <stdio.h>
#include <unistd.h>
int main()
{
int *p1 = sbrk(4); //返回空閑地址,并修改指針為+size
int *p2 = sbrk(0);
printf("%p\n", p1);
printf("%p\n", p2); //通過上面的程序分析,這里打印的
//是p1加上4個字節后的地址。int *p2 = sbrk(200);這句話括號里即使
//是200,p2也是p1加4個字節,因為sbrk是先返回當前的地址,再加括
//號里的size。如果括號里是負數,則表示釋放空間。
while(1);
}
下面再看brk(void *p)函數:
#include <stdio.h>
#include <unistd.h>
int main()
{
int *p = sbrk(0);
brk(p+1); //將sbrk里面的指針向后移動4個字節,發現沒
//有映射,就會自動映射區域。所以后面的*p就可以訪問了。
*p = 800;
brk(p); //將指針再移回去,相當于釋放內存空間,即取
//消之前的映射。后面再訪問就會出錯了。
*p = 29; //段錯誤。
while(1);
}
應用案例:
寫一個程序查找1-10000之間的所有的素數,并且存放到緩沖,然后打印。
分析:1-10000如果用數組的話,不太現實,有大部分空間都用不上。C++的話可以用鏈表實現,但是鏈表的開銷比較大。用malloc和new都不太好。所以,緩沖的實現使用sbrk/brk。
流程:
判斷是否是素數(isPrime)
是,分配空間存放
否,繼續下步
#include <stdio.h>
#include <unistd.h>
int isPrime(int a)
{
int i = 0;
for(i = 2; i < a; i++)
{
if(a%i == 0)
{
return 1;
}
}
return 0;
}
int main()
{
int i = 2; //循環變量
int *r;
int *p; //p一直指向頁首
r = sbrk(0);
p = r;
for(; i<10000; i++)
{
if(isPrime(i))
{
brk(r+1);
*r = i;
r = sbrk(0);
}
}
i = 0;
r = p;
while(r != sbrk(0))
{
printf("%d\n", *r);
r++;
}
brk(p); //釋放空間
}
總結:
new //C++里面用得比較多
malloc //C里面用得比較多,一定要制定空間大小
brk/sbrk //數據比較簡單,量比較大的時候用效率比較高
異常處理
int brk(void *) //返回int值
void *sbrk(int) //返回指針
如果成功,brk返回0, sbrk返回指針
如果失敗, brk返回-1, sbrk返回(void *)-1
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
void *p = sbrk(1000000000*3);
if(p == (void *)-1)
{
printf("error!");
perror("Hello"); //打印出錯誤信息
printf("%m"); //打印出memory error
printf("%s", strerror(errno));
}
}
以下是一些比較常用的函數:
字符串函數string.h cstring
內存管理函數malloc memset mamcmp memcpy…bzero
錯誤處理函數
時間函數
類型轉換函數
作業:
找出打印1-10000之間的所有孿生素數。