C函式中返回字元陣列
在閱讀本篇文章之前,建議大家看一下下面2篇文章:
這篇文章主要分享三個點:
1、為什麼作為區域性變數的字元陣列不能直接返回,而字元指標卻可以?
2、當字元陣列是區域性變數的時候,函式如何返回它?
3、字元陣列(char [])和字元指標(char *)如何互轉?
區域性變數的字元陣列
在C中如果我們直接返回字元陣列,編譯會直接報警告。如下示例:
char * fork_user_name() { char name[] = "veryitman"; return name; }
在Xcode中編譯警告資訊是這樣的:
Address of stack memory associated with local variable 'name' returned
在Linux上面GCC編譯顯示警告是這樣的:
warning: function returns address of local variable [-Wreturn-local-addr]
無論哪種警告資訊,基本意思都是告訴我們不應該返回一個區域性變數 name
的地址(函式內部的變數在棧記憶體上)。
如果我們修改一下程式碼,將 char
改為指標變數 char *
,示例如下:
char * fork_user_name2() { char *name = "veryitman"; return name; }
無論是Linux的GCC還是Xcode的Clang編譯器都不會報出警告。
首先我們要知道,常量是放在資料段裡面的。
這裡比較特殊,區域性變數 name
儲存在棧中,但是字串 veryitman
的值是一個常量,儲存在常量區。即便函式返回了,資料段裡面的常量資料也還不會消亡,它會直到程式結束才會消失,其記憶體空間直到程式執行結束才會被釋放。 所以,返回的地址是一個實際存在的有效地址。
char * fork_user_name() { char name[] = "veryitman"; return name; } char * fork_user_name2() { char *name = "veryitman"; return name; } int main() { printf("fork_user_name: %s\n", fork_user_name()); printf("fork_user_name2: %s\n", fork_user_name2()); return 0; }
用GCC編譯、執行後的列印結果,如下:
fork_user_name: (null) fork_user_name2: veryitman
總之,在函式中的區域性變數只要是返回類似 int[]
、 char[]
、 long[]
地址的,都是不正確的做法。
一切皆有可能
下面例子是不正確的,如下:
char * v_string() { char rest[10] = {'\0'}; return rest; }
1、使用 static
在C語言中,用 static
限定外部變數與函式,該外部變數或者函式除了對該所在的檔案可見外,其他檔案都無法訪問。 而用 static
宣告內部變數,則該變數是某個特定函式的區域性變數,只能在該函式中使用。但它與自動變數不同的是,不管其所在函式是否被呼叫,它一直存在,而不像自動變數那樣,隨著所在函式的被呼叫和退出而存在和消失。換句話說, static
型別的內部變數是一種只能在某個特定函式中使用但一直佔據儲存空間的變數。
所以使用static修飾一下,就沒有問題了。示例如下:
char * v_string() { static char rest[10] = {'\0'}; return rest; }
2、使用 malloc
這種方式可以解決這個問題,是因為使用 malloc
分配的記憶體是在堆上而不是在棧記憶體上面。但是要記得將其在呼叫方使用 free
釋放申請的記憶體空間,否則容易造成記憶體洩漏問題。
具體可以看看 雙宿雙飛的 malloc 和 free ) 這篇文章。
char * v_string() { char *p = (char *)malloc(10 * sizeof(char)); p = "\0"; return p; }
3、全域性變數
這個很好理解。全域性變數在程式真個生命週期中都是有效的,所以使用全域性變數也可以解決類似問題。
但是這種方案就會讓這個封裝的方法不夠內聚,因為它依賴了全域性變數。
char g_rest[100]; char * v_string() { strcpy(g_rest, "verytiamn"); return g_rest; }
4、返回形參指標變數
在Linux Kernel(核心原始碼版本5.0.7)中,函式 strcpy
的實現如下:
#ifndef__HAVE_ARCH_STRCPY /** * strcpy - Copy a %NUL terminated string * @dest: Where to copy the string to * @src: Where to copy the string from */ #undefstrcpy char *strcpy(char *dest, const char *src) { char *tmp = dest; while ((*dest++ = *src++) != '\0') /* nothing */; return tmp; } EXPORT_SYMBOL(strcpy); #endif
參考核心實現,我們可以修改一下自己的程式碼,示例如下:
char * v_string(char *s1, char *s2) { char *tmp = s1; // 省略... return tmp; }
這裡補充另外一個知識點,函式 strcpy
在glibc和Linux Kernel中實現不一樣。
在glibc的新版中(2.29版本),本質是呼叫了函式 memcpy
, 實現如下:
#include<stddef.h> #include<string.h> #undefstrcpy #ifndefSTRCPY #defineSTRCPY strcpy #endif /* Copy SRC to DEST. */ char * STRCPY(char *dest, const char *src) { return memcpy (dest, src, strlen (src) + 1); } libc_hidden_builtin_def (strcpy)
包括 strncpy
在glibc和Linux Kernel中實現也不一樣,有興趣的可以去看看原始碼。
字元陣列和字元指標的互轉
字元陣列轉字元指標即 char [] 轉 char *
這種情況下,可以直接進行賦值,示例如下:
int main() { char c_str_array[] = "veryitman.com"; char *p_str; p_str = c_str_array; printf("p_str: %s\n", p_str); return 0; }
字元指標轉字元陣列即 char * 轉 char []
是不是也可以直接進行賦值呢?擼段程式碼看看,如下:
int main() { char c_str_array[] = "veryitman.com"; char *p_str = "veryitman.com"; c_str_array = p_str; printf("c_str_array: %s\n", c_str_array); return 0; }
很遺憾,編譯報錯,GCC編譯錯誤截圖如下:
Clang編譯錯誤如下:
可以考慮使用 strncpy
來實現,示例程式碼如下:
#include<stdlib.h> #include<stdio.h> #include<string.h> int main() { char c_str_array[] = "veryitman.com"; char *p_str = "veryitman.com"; strncpy(c_str_array, p_str, strlen(p_str)); printf("c_str_array: %s\n", c_str_array); return 0 }
時間可以改變一切,但你得做點什麼!