1. 程式人生 > >字串記憶體中儲存位置

字串記憶體中儲存位置

原文連結:http://blog.csdn.net/yangdelong/article/details/5447362?reload
----------------------------------------------------------------------------------------------------
下面兩篇文章分析得比較詳細.

文章來源 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 中都引用了,但在記憶體中卻只有一份拷貝,這與靜態變數性質相當神似。
---------------------------------------------------------------------------------------------------------------
static作用,全域性儲存區和棧、堆:http://www.docin.com/DocinViewer-307721152-144.swf