指針與數組使用的區別(C語言)

一 函數不可返回指向棧內存的指針

預備知識:內存的分類

C/C++程序佔用的內存分爲兩大類:靜態存儲區與動態存儲區。其示意圖如下所示:
在這裏插入圖片描述
數據保存在靜態存儲區與動態存儲區的區別就是:靜態存儲區在編譯-鏈接階段已經確定了,程序運行過程中不會變化,只有當程序退出的時候,靜態存儲區的內存纔會被系統回收。動態存儲區是在程序運行過程中動態分配的。

在其它地方我們還可以看到內存分配還有其他分類,那些都是細分的分類,比如文字常量區、全局數據區等,都歸爲靜態存儲區這一個大類。

例子:return返回指向棧內存指針

先看一個return返回指向棧內存指針的例子:

#include <stdio.h>
char *GetStr(void)
{
    char ptr[] = "Hello"; /* 保存在棧中 */
    return ptr;
}

int main(void) 
{
    char *str = NULL;
    str = GetStr();
    printf("%s\n", str);
    return 0;
}

程序編譯、運行的結果如下:
在這裏插入圖片描述
可以看到,編譯出現警告:

warning: function returns address of local variable

運行結果並不是我們期望的輸出字符串Hello。

那是因爲GetStr函數返回指向棧內存的指針,這裏的變量ptr是局部變量,而局部變量是分配在棧上的。即Hello保存在棧內存上,棧內存在函數調用結束時會自動銷燬,因此此時的ptr裏的內容是未知的,所以結果無輸出。

下面我們把GetStr函數修改爲:

char *GetStr(void)
{
    char *ptr = "Hello";  /* ptr在棧上,Hello在靜態區(常量區) */
    return ptr;
}

此時編譯運行的結果是怎樣的呢?結果爲:
在這裏插入圖片描述
可以看到能正常輸出。爲什麼這裏又可以正常輸出呢?因爲這裏的ptr雖然分配在棧上,但是此時的Hello是一個字符串常量,其存儲在靜態存儲區。在調用GetStr函數結束時其也不會被銷燬。

這裏可能有些人會有疑惑,同樣是Hello,爲什麼一個在棧上,一個在靜態區。

char *ptr = “Hello”;
此處首先定義了一個指針變量ptr,編譯器就會爲指針變量開闢了棧空間。而此時並沒有空間來存放Hello,所以Hello只能存儲在靜態區。

char ptr[] = “Hello”;
此處首先定義一個數組ptr,因爲未給出數組大小,所以此時數組大小未確定。然後把Hello保存在這個數組裏,編譯器就會爲數組ptr開閉足夠的棧空間來存儲Hello。

其它替代方法

從上面的例子我們知道,若函數返回指向棧內存的指針,所得到的結果並不是我們想要的。除了上面的方法之外,這裏還有如下幾種解決方法:

1、把ptr定義爲全局變量,因爲全局變量存儲在靜態存儲區,程序結束纔會釋放。但是這樣會導致函數是不可重入的。關於函數的重入與不可重入可查看往期筆記:什麼是可重入函數?

2、在GetStr函數中使用malloc申請動態內存,但使用完一定要記得使用free進行釋放,否則會導致內存泄漏。示例代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char GetStr(void)
{
char ptr = (char)malloc(64
sizeof(char));
strcpy(ptr, “Hello”);
return ptr;
}

int main(void)
{
char str = NULL;
str = GetStr();
printf("%s\n", str);
free(str); /
釋放str指向的堆內存 */
return 0;
}
3、可以將變量ptr聲明爲static靜態變量。但這也會導致函數是不可重入的。示例代碼如下:

char *GetStr(void)
{
static char ptr[] = “Hello”;
return ptr;
}

二 指針與數組區別

C語言中沒有特定的字符串類型,常用以下兩種方式定義字符串:一種是字符數組,另一種是指向字符串的指針。如下:

(1)char str[] = "happy";
(2)char *str = "happy";

這種方式有什麼不同呢?
下面看兩個例子:修改字符串中的字符
示例1:

#include <stdio.h>
int main(void)
{
 char str[20] = "hello";
 
 str[0] = 'H';
 printf("%s\n",str);
 
 return 0;
}

運行結果:

Hello

示例2:

#include <stdio.h>

int main(void)
{
 char *str = "hello";
 
 str[0] = 'H';
 printf("%s\n",str);
 
 return 0;
}

運行結果:

無打印信息輸出

可見,使用(1)方式定義的字符串其字符是可以修改的,使用(2)方式定義的字符串其字符是不可以修改的。(2)中可以成功編譯和鏈接,但運行時可能會出現錯誤,我編譯與運行的平臺是window10平臺,運行結果是無打印信息輸出,在其他不同的平臺運行可能會出現段錯誤(Segment Fault)或者寫入位置錯誤。

這兩種表示字符串的方式的主要區別是:字符串指針指向的內容是不可修改的,字符數組是可以修改的,即(2)方式定義的字符串保存在常量區,是不可更改的,(1)方式定義的字符串保存在全局數據區或棧區,是可修改的。

內存的分配方式:
內存分配可分爲三種:靜態存儲區、棧區、堆區。
1、靜態存儲區:該內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在,它主要存放靜態數據、全局數據和常量。
2、棧區:它的用途是完成函數的調用。在執行函數時,函數內局部變量及函數參數的存儲單元在棧上創建,函數調用結束時這些存儲單元自動被釋放。
3、堆區:程序在運行時使用庫函數爲變量申請內存,在變量使用結束後再調用庫函數釋放內存。動態內存的生存期是由我們決定的,如果我們不釋放內存,就會導致內存泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章