C/C++記憶體儲存問題是筆試中必須掌握的。先看下面的程式:
#include <stdio.h>
#include "string.h"
#include "malloc.h"
void Swap(int a,int b)
{
int temp;
temp=a;
a=b;
b=temp;
}
int Get_Int(int a)
{
int i=1+a;
return i;
}
char* Get_Memory0()
{
char* p=(char*)malloc(sizeof(char)* 20);
strcpy(p,"hello world");
return p;
}
char* Get_Memory1()
{
char* p="hello world";
return p;
}
char* Get_Memory2()
{
char p[]="hello world";
return p;
}
void main()
{
int x=4,y=3;
Swap(x,y);
int z=x-y;
printf("z=%d\n",z);
z=Get_Int(z);
printf("z=%d\n",z);
char* c0=Get_Memory0();
printf("c0=%s\n",c0);
const char* c1=Get_Memory1();
printf("c1=%s\n",c1);
char* const c2=Get_Memory2();
printf("c2=%s\n",c2);
}
基礎知識
記憶體儲存:
在C/C++中,通常可以把記憶體理解成4個分區:棧、堆、全域/靜態儲存區和常量儲存區。
(1)棧:通常是用那些在編譯期間就能確定其儲存大小的變數的儲存區,用於在函數範圍內建立、在離開範圍後自動銷毀的變數的儲存區。通常是局部變數、函數參數等的儲存區。它的儲存空間是連續的,兩個緊挨著的定義的局部變數,它們的儲存空間是緊挨著的。棧的大小是有限的,通常Visual C++編譯器預設棧的大小是1M,所以不要定義int a[1000000]這樣的超大數組。
(2)堆:通常是用於那些在編譯期間不能確定儲存大小的變數儲存區,它的儲存空間是不連續的,一般同malloc(或new)函數來分配記憶體塊,並且需要要free(或delete)釋放記憶體。如果程式員沒有釋放掉,那麼就會出現常說的記憶體流失問題。需要注意的是,兩個緊挨定義的指標變數,所指向的malloc出來的記憶體並不一定是緊挨著的。另外需要注意的一點是,堆的大小几乎是不受限制的,理論上每個程式最大可達4GB。
(3)全域/靜態儲存區:和“棧”一樣,通常是用於那些在編譯期間就能確定儲存大小的變數的儲存區,但它用於的是在整個程式運行期間都可見的全域變數和靜態變數。
(4)常量儲存區:和“全域/靜態儲存區”一樣,通常是用於那些在編譯期間就能確定儲存大小的常量儲存區,並且在程式運行期間,儲存區內的常量也是全域可見的。這是一塊比較特殊的儲存區,它們裡面放的是常量,不允許被修改。
程式解答:
(1)Swap函數:我們知道,所有函數都會在運行時從程式“棧”上得到分配給它的一Block Storage區(這裡的“棧”就是前兩面講解記憶體儲存時提到的“棧”,第個運行程式都擁有自己的運行時“棧”)。這塊“棧”上的函數儲存區隨函數的開始面開始,隨著函數的結束而結束。函數結束後,這Block Storage區就會自動釋放,以供程式的其他用途使用。系統在函數運行時會為函數的每一個參數都提供儲存區,參數在儲存區中的儲存長度由自身的類型決定。參數傳遞,就是系統用函數實參初始化函數參數儲存區的過程。
函數的參數有傳值(傳指標)和傳址(傳引用)兩種。所謂參會傳值,就是實參的值複製到函數運行時分配給函數的參數儲存區中。參數在傳值時,函數不會訪問當前調用的實參,函數處理的是實參在本地的拷貝,這些拷貝被儲存在函數的“棧”中,所以這些拷貝值的改變不會影響實參的值。
答案:
z=1
(2)問題:傳回值i是一個局部變數,函數的返回參數怎麼能是局部變數呢?局部變數在離開範圍後就自動被銷毀了,還怎麼能傳回值給調用都呢?
解答:函數的傳回值有傳值和傳址兩種。int Get_Int(int a)屬於傳回值是傳值的函數,這就意味著函數int Get_Int(int a)會在函數返回處產生一個臨時對象,用於存放局部變數i的值的一份拷貝(變數i的右值的拷貝),臨時對象是沒有名稱的,這份沒有名稱的對象的值(右值)會儲存在調用者的“棧”中。所以當i作為局部變數離開範圍後,雖然被銷毀了,但它的拷貝仍然存在,並在函數返回時作為“右值”賦給“左值z”。
答案:
z=2
(3)此處考查對“堆”的理解:函數返回的是指向“堆”記憶體的指標。程式中malloc()是用於分配“堆”記憶體的庫函數,而對於“堆”記憶體,只要程式中沒有調用free()庫函數去釋放掉該“堆”記憶體,那麼在程式運行期間,malloc()庫函數分配的“堆”記憶體將一直存在。
char* p=(char*)malloc(sizeof(char)*20)表示分配一塊“堆”記憶體並使得變數p指向這塊“堆”記憶體。
strcpy(p,"hello world")表示往p指向的“堆”記憶體中複製字串hello world。
return p,變數p的左值是一個局部變數指標,儲存於函數棧上,右值 是“堆”的地址(“堆”的值是hello world)。
函數char* Get_Memory0()屬於傳回值是傳指標的函數,這就意味著函數char* Get_Memory0()會在函數返回處產生一個對返回變數p的“左值”的拷貝,也就是在“左值”的拷貝中儲存了指向“堆”的地址。作為局部變數的 p,在離開函數範圍的時候雖然被銷毀了,但函數傳回值珠“左值”的拷貝是存在的,該拷貝儲存了指向“堆“的地址,而而該“堆”的值是hello world。
答案:
c1=hello world
(4)這裡考查對函數傳回值和指標的理解:變數分為左值和右值,在char* p="hello world"中,左值是局部變數指標p,儲存於函數棧上,右值是字串常量hello world,儲存於常量儲存區。
char* Get_Memory1()屬於傳回值是傳指標的函數,這就意味著函數char* Get_Memory1()會在函數返回處產生一個對返回對象的“左值”的拷貝,也就是在“左值”拷貝中儲存了指向字串常量hello world的地址。作為局部變數的p,在離開函數範圍的時候雖然被銷毀了,但函數 返回的“左值”拷貝仍然儲存了指向常量儲存區的字串常量hello world的地址。
答案:
c1=hello world
(5)和問題(4)有些相似,考查的也是對函數傳回值和指標的理解:傳回值是傳指標,會在函數返回時產生“左值”拷貝。可以看到,p[]不是指標,是一個陣列變數。編譯器根據右值“hello world”的長度(12個字元)在編譯期間在函數的棧上為數組分配大小為12個字元的記憶體儲存區,其值是“hello world”。
通常,變數的意義在於,它給一塊記憶體儲存區提供名字,方便程式對這塊記憶體進行讀寫。變數包含兩個值:左值和右值。左值是記憶體儲存區的名字,右值是存放儲存區中的值。從程式中可以看到,函數返回的“左值”拷貝 指向的是局部變數數組p[12]的首地址。當局部數組p[12]範圍後會被自動銷毀。這時,函數返回的“左值”拷貝指向的是一個被銷毀的局部變數地址。
答案:
warning C4172: returning address of local variable or temporary
c2=未知