2009-01-17 85 views
35

我正在使用一些廣泛使用返回指向靜態局部變量指針的代碼。例如:正在返回一個指向靜態局部變量安全的指針嗎?

char* const GetString() 
{ 
    static char sTest[5]; 
    strcpy(sTest, "Test"); 
    return sTest; 
} 

我是否認爲這是安全的?

PS,我知道,這將是做同樣的事情的一個更好的方法:

char* const GetString() 
{ 
    return "Test"; 
} 

編輯: 道歉,函數簽名當然應該是:

const char* GetString(); 

回答

32

第一個例子:有些安全

char* const GetString() 
{ 
    static char sTest[5]; 
    strcpy(sTest, "Test"); 
    return sTest; 
} 

雖然不建議,這是安全的,即使函數的作用域結束,靜態變量的作用域仍然保持有效。這個函數根本不是線程安全的。一個更好的函數會讓你通過char* buffermaxsizeGetString()函數來填充。

特別是,該功能並不算是折返功能因爲重入函數不絕,除其他事項外,返回地址靜態(全球)非恆定的數據。見reentrant functions

第二個例子:完全不安全

char* const GetString() 
{ 
    return "Test"; 
} 

,如果你做了const char *這將是安全的。 你給的東西不安全。原因是因爲字符串文字可以存儲在只讀內存段中並允許它們被修改將導致未定義的結果。

char* const(常量指針)意味着您不能更改指針指向的地址。 const char *(指向const的指針)表示不能更改此指針指向的元素。

結論:

您應該考慮之一:

1)如果你有機會獲得代碼,然後修改GetString採取char* buffer的參數,以填補和maxsize使用。 2)如果您無法訪問代碼,但必須調用它,請將此方法包裝在另一個受互斥鎖保護的函數中。新方法如1所述。

6

static變量(在一個函數中)就像範圍內的全局變量。一般來說,它們應該避免(像全局變量,它們會導致重入問題),但有時會很有用(一些標準庫函數使用它們)。你可以返回指向全局變量的指針,所以你也可以返回指向static變量的指針。

+1

「一般來說,他們應避免」可能是太強大了,但可以肯定的是你應該意識到風險和限制。爲了澄清_why_其確定的+1。 – dmckee 2009-01-17 18:15:02

+0

我同意dmckee,重入問題是因爲在函數調用中靜態設計是活着的。這不是不好的行爲。但你的確應該知道風險。 – 2009-01-17 18:18:44

0

是的,這經常用於返回某些查找的文本部分,即將一些錯誤號碼轉換爲人類友好的字符串。

其明智做到這一點的情況下,您倒是:

fprintf(stderr, "Error was %s\n", my_string_to_error(error_code)); 

如果my_string_to_error()返回一個分配的字符串,程序會泄漏給這樣的功能上面(非常)常見的用法。

char const *foo_error(...) 
{ 
    return "Mary Poppins"; 
} 

......也行,一些大腦死亡的編譯器可能希望你把它施放。

只看這種方式的字符串,不要歸還書:)

2

是的,它是完全安全的。本地靜態的生命週期是整個程序在C中的執行時間,所以你可以返回一個指向它的指針,因爲即使在函數返回之後數組仍然是活着的,並且返回的指針可以有效地去引用。

9

從根本上說,是的,它是安全的,因爲它是靜態的,它的值將無限期地持續下去。

從某種意義上說,您已經返回了一個指向變量數據的常量指針,而不是指向常量數據的變量指針,這並不安全。這是更好,如果調用函數不允許修改數據:

const char *GetString(void) 
{ 
    static char sTest[5]; 
    strncpy(sTest, "Test", sizeof(sTest)-1); 
    sTest[sizeof(sTest)-1] = '\0'; 
    return sTest; 
} 

在簡單情況下所示,幾乎沒有必要擔心緩衝區溢出,雖然我的代碼版本不擔心,並確保空終止。另一種方法是使用TR24731功能strcpy_s代替:

const char *GetString(void) 
{ 
    static char sTest[5]; 
    strcpy_s(sTest, sizeof(sTest), "Test"); 
    return sTest; 
} 

更重要的是,這兩個變種返回一個(變量)指針常量數據,因此用戶不應該去修改字符串和(可能)踐踏外陣列的範圍。 (As @strager在註釋中指出,返回const char *並不保證用戶不會嘗試修改返回的數據,但是他們必須轉換返回的指針,使其不是const,然後修改數據;這會調用未定義的行爲,任何事情都有可能發生。)

字面返回的一個優點是通常可以通過編譯器和操作系統來強制執行不寫的承諾。該字符串將被放置在程序的文本(代碼)段中,並且如果用戶嘗試修改返回值所指向的數據,操作系統將生成一個錯誤(Unix上的分段違例)。

[至少有一個答案指出代碼不可重入;那是對的。返回文字的版本是可重入的。如果重入是很重要的,接口需要被固定,這樣,來電提供了數據存儲的空間]

+0

這不是一個承諾:這是一個建議。你可以拋棄const。你是對的,它可能應該是const char *而不是char * const,但我不確定函數返回值的含義是否不同。 – strager 2009-01-17 18:25:34

7

這取決於你所說的安全。我可以立即看到幾個問題:

  1. 您已經返回了char * const,這將允許調用者更改此位置的字符串。潛在的緩衝區溢出。或者你的意思是const char *
  2. 您可能會遇到再入或併發問題。

爲了解釋第二,考慮一下:

const char * const format_error_message(int err) 
{ 
    static char error_message[MAXLEN_ERROR_MESSAGE]; 
    sprintf(error_message, "Error %#x occurred", err); 
    return error_message; 
} 

如果你這樣稱呼它:

int a = do_something(); 
int b = do_something_else(); 

if (a != 0 && b != 0) 
{ 
    fprintf(stderr, 
     "do_something failed (%s) AND do_something_else failed (%s)\n", 
     format_error_message(a), format_error_message(b)); 
} 

...這是怎麼回事要打印?

線程相同。

1

這是非常有用的,因爲您可以直接使用該函數作爲printf參數。 但是,如前所述,在單個調用中對函數進行多次調用會導致問題,因爲該函數使用相同的存儲並調用它兩次將覆蓋返回的字符串。但是我測試了這段代碼,它似乎可以工作 - 您可以安全地調用一個函數,其中最多MAX_CALLS次使用givemestring,它將表現正確。

#define MAX_CALLS 3 
#define MAX_LEN 30 

char *givemestring(int num) 
{ 
     static char buf[MAX_CALLS][MAX_LEN]; 
     static int rotate=0; 

     rotate++; 
     rotate%=sizeof(buf)/sizeof(buf[0]); 

     sprintf(buf[rotate],"%d",num); 
     return buf[rotate]; 

} 

唯一的問題是線程安全的,但是可以使用線程局部變量(gcc的__thread關鍵字)來解決

相關問題