2009-01-17 95 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关键字)来解决

相关问题