1.基本概念
堆(Heap)
程序可以動態申請的存儲空間,通過malloc系列函數分配,全局可訪問。棧(Stack)
這里說的棧不是數據結構中LIFO的棧,而是進程虛擬地址空間的棧;程序在進行函數調用時動態伸縮的存儲空間,局限于函數內可以訪問。
-堆變量(Heapvariables)
數據存儲在堆的變量,全局可訪問。
棧變量(Stackvariables)
函數中聲明的局部變量,只能在函數內部訪問,否則訪問行為的結果是未定義的。指針參數(Pointerparameters)
參數類型為指針的參數。非指針參數(Non-pointerparameters)
參數類型不是指針的參數。
2.進程的虛擬地址空間
進程虛擬地址空間
3.相關問題剖析
3.1 如何正確的分配內存
demo代碼
void * fun_m1()
{
char buf[100];
return (void *)buf;
}
void * fun_m2(size_t size)
{
return malloc(size);
}
void fun_m3(size_t size, void * p)
{
p = malloc(size);
}
void fun_m4(size_t size, void ** p)
{
*p = malloc(size);
}
剖析
- fun_m1是錯誤的,因為它返回的棧變量的地址,如果對它指向的地址進行讀寫,程序行為的結果是未定義的,程序很可能崩潰,因為此時棧變量的空間已經被回收(棧頂指針改變了)。
- fun_m2是正確的,因為它返回的是malloc申請的堆空間的地址。
- fun_m3和fum_m4很具有迷惑性,要分區fun_m3和fun_m4的區別,我們這里需要澄清一個概念:任何的參數傳遞本質上都是值拷貝,任何參數都是棧變量。
- 在我們以往觀念中參數傳遞就是兩種:值傳遞,指針傳遞,而通過指針可以改變指針指向的變量。
- 為什么說參數傳遞都是值拷貝呢,這是因為不管參數是否為指針,傳遞的都是一份值的拷貝,只不過當你的參數類型為指針時,你傳遞的是指針變量的值,而通過*操作符作用在指針變量上,你又剛好可以影響到指針變量關聯的其他變量的值。
- 通過上面的剖析我們可以知道fun_m3是錯誤的,因為沒有*操作符作用在p上,單純對p賦值操作只會影響到局部的棧變量p的值,而不會對函數fun_m3外的變量有任何影響。fun_m4是正確的,因為有*操作符作用在p上,通過對*p賦值來修改傳遞給p的參數,使它指向申請的堆空間。
3.2 如何判斷堆和棧的“增長”方向(從低到高,還是從高到低)
判斷棧的“增長”方向
#include <stdio.h>
#include <malloc.h>
void fun1(int * pb)
{
int a;
printf("stack alloc direction[%s]\n", &a > pb ? "Up" : "Down");
}
void fun2()
{
int b;
fun1(&b);
}
int main()
{
fun2();
return 0;
}
編譯運行
[root@iZ940zytujjZ test]# gcc -o test10 test10.c
[root@iZ940zytujjZ test]# ./test10
stack alloc direction[Down]
[root@iZ940zytujjZ test]#
從運行結果看,棧的增長方向是“從高到低”(Down)。
判斷堆的“增長”方向
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
int main()
{
void * a = sbrk(10); //調整堆頂指針brk
void * b = sbrk(20);
printf("heap alloc direction[%s]\n", b > a ? "Up" : "Down");
return 0;
}
編譯運行
[root@iZ940zytujjZ test]# gcc -o test11 test11.c
[root@iZ940zytujjZ test]# ./test11
heap alloc direction[Up]
[root@iZ940zytujjZ test]#
從運行結果看,堆的增長方向是“從低到高”(Up)。