下面兩篇文章分析得比較詳細.轉自http://blog.csdn.net/yangdelong/archive/2010/04/03/5447362.aspx
文章來源 http://blog.csdn.net/shiwei0124/archive/2009/11/26/4877546.aspx
字串的儲存方式以及靜態儲存地區、棧、堆
在編程的時候偶爾會遇到一個字串的問題,好像是這樣說:不能把 const char* 轉換成 TCHAR * ,只是這個錯誤有時候有,有時候沒有,也沒有深入關注過,只知道 "abc" 應該是一個const 型的。
今天偶然看到2個文章,終於對這個問題有了比較清晰的理解
貼一:
http://topic.csdn.net/u/20090302/17/900b3797-3642-4569-a623-dc0f8ebd8401.html?seed=1325371970
#include <stdio.h>
int A()
{
int test=10;
return test;
}
int main()
{
int a=A();
printf("%d/n",a);
return 0;
}
上面的代碼能編譯通過 我想問 在A() 函數中的 test 變數的生存期不是只在A()函數體內嗎? 怎麼還能成功返回呢
下面的這段代碼為何就不行呢 兩個程式中的變數生存期有什麼區別啊?
#include <stdio.h>
char* A()
{
char p[]="hello world";
return p;
}
int main()
{
char *str=NULL;
str=A();
printf("%s",str);
}
比較好的答案是:
一:
關鍵在這裡:
int a=A();
實際是a=test;之後test掛掉了,我們不管,反正有a.
而
str=A();
就是str=p.
之後p跟p指向的堆區都掛了,可是str依然孤獨地指向p那片掛掉的堆區.
明白了不?
二:
注意
int A()
{
int test=10;
return test;
}
返回的是值, 的確調用完後test會釋放, 但它的值被返回了
而
char* A()
{
char p[]="hello world";
return p;
}
返回的是指標, 確切講是指標的值, 但因為"hello world"會釋放, 所以返回的這個指標值沒用了
(存放字串的空間被釋放掉了, 這個指標值還有什麼用?)
然後在看第二帖:
http://topic.csdn.net/u/20091126/10/8e6dfe37-b12f-410f-9e02-83eaad1c30a0.html?25692
問題:
先看貼http://topic.csdn.net/u/20090302/17/900b3797-3642-4569-a623-dc0f8ebd8401.html?seed=1325371970
這個文章裡面斑竹回答的內容,感覺很深刻。正確。但是實驗一下時候出現點問題
C/C++ code
char *FuncC()
{
char* a="hello word";
return a;
}
char *FuncB()
{
char a[]="hello word";
return a;
}
int _tmain(int argc, _TCHAR* argv[])
{
char *b,*c;
c = FuncC();
b = FuncB();
char a[100];
::memset(a,NULL,100);
strcpy(a,c);
std::cout<<"A="<<a <<" B="<<b<<" C ="<<c<<endl;
getchar();
return 0;
}
輸出結果:
C/C++ code
A=hello word B=p_/*(操作未知)*/ C=hello word;//我也認為C結果也是未定義的,但是不管怎麼操作,結果都對啊
唯一不同的是 FuncB中,返回的是局部指標數組的頭指標,FuncC中返回的是局部指標的副本。他們都在編譯時間候確定大小,分配空間,他們都指向的是字串常量。為啥FuncB失敗。FuncC就成功呢。
還是,還是說,結果未知,即使輸出正確,。。
回答:
一:
不知道樓主對棧,堆,靜態區瞭解多少,
FuncC中,char* a="hello word";
a並沒有分配空間,而是"hello word"這個串放在了靜態區(和全域變數一個層級),放在棧中的只是指標a(4個位元組的指標),函數返回後,銷毀的也只是a,靜態區的串是不會銷毀的
c = FuncC();
雖然a被銷毀了,但c接收了傳回值,依然指向靜態區的串"hello word"
所以是沒有問題的
相反的FuncB()中
char a[]="hello word";
整個串都是放在棧中的,函數返回時被銷毀了,結果未知。
二:
FuncA的做法返回的是一個指標,這個指標指向的是待用資料區,函數返回時得到這個指標的拷貝,其指向的位置仍是待用資料區裡"hello word"的入口。
FuncB的做法相當於在函數的棧內申請了一個資料區存放這個資料,a[]="hello word"; 存在一個資料拷貝的操作,是將待用資料區內的資料拷貝到棧資料區內,當函數退出後,如果系統只是簡單地將棧頂指標回退,那可能a指向的資料仍然是"hello word",如果系統在函數退出時是先把棧的資料都抹掉再回退棧頂指標,那a指向的資料就是未知的了。
可以看到 char a[] = "sdaf" 和 char *a = "sdaf" 雖然表面上看差不多,其實機制是不一樣的,差異就在"sdaf"這個字串儲存的方式不一樣
下面在看一段“關於靜態儲存地區、棧、堆”的介紹
有關動態對象建立:一般來說,編譯器將記憶體分為三部分:靜態儲存地區、棧、堆。靜態儲存區主要儲存 全域變數和靜態變數,棧儲存調用函數相關的變數、地址等,堆儲存動態產生的變數,在c中是指由malloc,free運算產生釋放的儲存空間,在c++中 就是指new和delete運算子作用的儲存地區。
1、 靜態儲存分配
指在編譯時間對資料對象分配固定的儲存位置,運行時始終不變。即一旦儲存空間的某個位置分配給了某個資料名,則在目標程式的整個運行過程中,此位置(地址)就屬於該資料名。
由靜態儲存分配產生的資料區稱為待用資料區。
靜態儲存分配適用於不允許遞迴過程或遞迴調用,不允許可變體積的資料結構的語言
靜態儲存分配的特點:簡單、易於實現
例:FORTRAN語言,它所有的資料都屬於這一類。
2、 動態儲存裝置分配
指在運行階段動態地為來源程式中的資料對象分配儲存位置
實行動態儲存裝置分配的語言的特點:
允許遞迴過程
允許可變資料結構(可變數組或記錄等)
允許使用者自由申請和釋放空間
這種程式在編譯時間無法確定運行時所需資料空間的大小,需待程式運行時動態確定
有兩種動態儲存裝置分配方式:棧式(stack)、堆式(heap)。
3、 棧式動態儲存裝置分配
在資料空間中開闢一個棧區,每當調用一個過程時,它所需要的資料空間就分配在棧頂,每當過程工作結束時就釋放這部分空間。空間的使用符合先借後還的原則。
特點:先借後還,管理簡單,空間使用效率高
棧式動態儲存裝置分配適合於PASCAL、C等典型過程式語言。
4、 堆式動態儲存裝置分配
在資料空間中開闢一片連續的儲存區(通常叫做堆),每當需要時就從這片空間借用一塊,不用時再退還。借用與歸還未必服從“先借後還”的原則。
堆式動態儲存裝置分配適合於使用者可以自由申請和歸還資料空間的語言,如C++。
特點:適用範圍廣,容易出現片段。
如何充分利用空間是個難題。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/shiwei0124/archive/2009/11/26/4877546.aspx
文章來源 http://hi.baidu.com/%D0%C7%BB%F0%D3%C4%C0%B6/blog/item/410174384e529ffbb211c71c.html
常量字串為什麼位於靜態儲存區?
char *c="chenxi";
書上說: "chenxi"這個字串被當作常量而且被放置在此程式的記憶體靜態區。
那一般的int i=1;
1也是常量,為什麼1就不被放置在此程式的記憶體靜態區了呢?
請高手指點!
所有的字元竄常量都被放在靜態記憶體區
因為字串常量很少需要修改,放在靜態記憶體區會提高效率
例:
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
結果是:0 0 1 1
str1,str2,str3,str4是陣列變數,它們有各自的記憶體空間;
而str5,str6,str7,str8是指標,它們指向相同的常量地區。
問題的引入:
看看下面的程式的輸出:
#include <stdio.h>
char *returnStr()
{
char *p="hello world!";
return p;
}
int main()
{
char *str=NULL;//一定要初始化,好習慣
str=returnStr();
printf("%s/n", str);
return 0;
}
這個沒有任何問題,因為"hello world!"是一個字串常量,存放在待用資料區,
把該字串常量存放的待用資料區的首地址賦值給了指標,
所以returnStr函數退出時,該該字串常量所在記憶體不會被回收,故能夠通過指標順利無誤的訪問。
但是,下面的就有問題:
#include <stdio.h>
char *returnStr()
{
char p[]="hello world!";
return p;
}
int main()
{
char *str=NULL;//一定要初始化,好習慣
str=returnStr();
printf("%s/n", str);
return 0;
}
"hello world!"是一個字串常量,存放在待用資料區,沒錯,
但是把一個字串常量賦值給了一個局部變數(char []型數組),該局部變數存放在棧中,
這樣就有兩塊內容一樣的記憶體,也就是說“char p[]="hello world!";”這條語句讓“hello world!”這個字串在記憶體中有兩份拷貝,一份在動態分配的棧中,另一份在靜態儲存區。這是與前者最本質的區別,
當returnStr函數退出時,棧要清空,局部變數的記憶體也被清空了,
所以這時的函數返回的是一個已被釋放的記憶體位址,所以列印出來的是亂碼。
如果函數的傳回值非要是一個局部變數的地址,那麼該局部變數一定要申明為static類型。如下:
#include <stdio.h>
char *returnStr()
{
static char p[]="hello world!";
return p;
}
int main()
{
char *str=NULL;
str=returnStr();
printf("%s/n", str);
return 0;
}
這個問題可以通過下面的一個例子來更好的說明:
#include <stdio.h>
//返回的是局部變數的地址,該地址位於動態資料區,棧裡
char *s1()
{
char* p1 = "qqq";//為了測試‘char p[]="Hello world!"’中的字串在靜態儲存區是否也有一份拷貝
char p[]="Hello world!";
char* p2 = "w";//為了測試‘char p[]="Hello world!"’中的字串在靜態儲存區是否也有一份拷貝
printf("in s1 p=%p/n", p);
printf("in s1 p1=%p/n", p1);
printf("in s1: string's address: %p/n", &("Hello world!"));
printf("in s1 p2=%p/n", p2);
return p;
}
//返回的是字串常量的地址,該地址位於待用資料區
char *s2()
{
char *q="Hello world!";
printf("in s2 q=%p/n", q);
printf("in s2: string's address: %p/n", &("Hello world!"));
return q;
}
//返回的是靜態局部變數的地址,該地址位於待用資料區
char *s3()
{
static char r[]="Hello world!";
printf("in s3 r=%p/n", r);
printf("in s3: string's address: %p/n", &("Hello world!"));
return r;
}
int main()
{
char *t1, *t2, *t3;
t1=s1();
t2=s2();
t3=s3();
printf("in main:");
printf("p=%p, q=%p, r=%p/n", t1, t2, t3);
printf("%s/n", t1);
printf("%s/n", t2);
printf("%s/n", t3);
return 0;
}
運行輸出結果:
in s1 p=0013FF0C
in s1 p1=00431084
in s1: string's address: 00431074
in s1 p2=00431070
in s2 q=00431074
in s2: string's address: 00431074
in s3 r=00434DC0
in s3: string's address: 00431074
in main:p=0013FF0C, q=00431074, r=00434DC0
$
Hello world!
Hello world!
這個結果正好應證了上面解釋,同時,還可是得出一個結論:
字串常量,之所以稱之為常量,因為它可一看作是一個沒有命名的字串且為常量,存放在待用資料區。
這裡說的待用資料區,是相對於堆、棧等動態資料區而言的。
待用資料區存放的是全域變數和靜態變數,從這一點上來說,字串常量又可以稱之為一個無名的靜態變數,
因為"Hello world!"這個字串在函數 s1和s2 中都引用了,但在記憶體中卻只有一份拷貝,這與靜態變數性質相當神似。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/yangdelong/archive/2010/04/03/5447362.aspx
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/jdr826857029/archive/2010/10/10/5931106.aspx