返回指向静态局部变量的指针是否安全?

57

我正在处理一些代码,其中广泛使用返回指向静态局部变量的指针的惯用法。例如:

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

我是否正确地认为这是安全的?

顺便说一句,我知道这样做的更好方式是:

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

编辑:抱歉,函数签名应该是:

const char* GetString();
7个回答

43

第一个示例:有点安全

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

虽然不推荐这样做,但是它是安全的。静态变量的范围即使在函数结束后仍然存在。但是该函数并不是非常线程安全的。更好的方法是让你传递一个char* buffermaxsizeGetString()函数中进行填充。

特别地,这个函数不被认为是可重入函数,因为可重入函数不能,除其他事项外,返回指向静态(全局)非常量数据的地址。请参阅可重入函数

第二个例子:十分不安全

char* const GetString()
{
  return "Test";
}
如果你使用 const char *,那么这将是安全的。而你所提供的方法不安全的原因是字符串文字可以存储在只读内存段中,允许对其进行修改将导致未定义的结果。 char* const(常量指针)意味着你不能更改指针指向的地址。 const char *(指向常量的指针)意味着你不能更改该指针指向的元素。
结论:
你应该考虑以下两种方法之一:
1)如果你有代码访问权限,则修改 GetString 方法,使其接受一个参数为 char* buffer 的缓冲区并使用 maxsize 填充。
2)如果你没有访问代码的权限,但必须调用它,则应在另一个受互斥锁保护的函数中包装此方法。新方法与 1 中描述的方法相同。

12

static变量(在函数内)就像作用域限定的全局变量。通常应该避免使用它们(像全局变量一样,它们会导致可重入性问题),但有时它们是很有用的(某些标准库函数使用它们)。你可以返回指向全局变量的指针,因此你也可以返回指向static变量的指针。


3
“总的来说,他们应该被避免”可能有些强烈,但确实需要注意其风险和局限性。关于为什么它是可以接受的,可以更清晰地解释一下。 - dmckee --- ex-moderator kitten
1
我同意dmckee的观点,可重入性问题是由于静态变量在函数调用之间保持活动状态的设计所致。这并不是不良行为,但你应该意识到其中的风险。 - Johannes Schaub - litb

10

这取决于你对“安全”的定义。我能立即看到几个问题:

  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));
} 

打印的结果是什么?

对于线程也是一样。


8

从根本上说,是的,它是安全的,因为它是静态的,价值将永久存在。

从另一个角度来看,它并不安全,因为你返回了指向变量数据的常量指针,而不是指向常量数据的可变指针。如果调用函数不被允许修改数据,则更好:

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;
}

更重要的是,这两个变量都返回一个指向常量数据的(可变)指针,因此用户不应该修改字符串并(可能)超出数组的范围。(正如@strager在评论中指出的那样,返回一个const char *并不能保证用户不会尝试修改返回的数据。但是,他们必须将返回的指针强制转换为非const并修改数据;这会引发未定义的行为,在那一点上任何事情都有可能发生。)
字面值返回的一个优点是,不写承诺通常可以由编译器和操作系统强制执行。该字符串将被放置在程序的文本(代码)段中,如果用户尝试修改由返回值指向的数据,则操作系统将生成故障(在Unix上进行分段违规)。
[至少另一个答案指出代码不可重入;这是正确的。返回文字的版本是可重入的。如果可重入性很重要,则需要修复接口,以便调用者提供存储数据的空间。]

这不是承诺,而是建议。你可以取消const限定符。你说得对,它可能应该是const char *而不是char * const,但我不确定对于函数返回值是否有不同的含义。 - strager
@strager:是的,你可以强制编译器滥用返回值。在“char *const”中,const没有任何好处;一旦该值被分配给一个单独的(非const)指针变量,那个指针就可以被修改;而返回值永远不能被修改。 - Jonathan Leffler

3

是的,这是完全安全的。在C语言中,局部静态变量的生命周期是整个程序执行期间。因此,您可以返回指向它的指针,因为即使函数返回后数组仍然存在,返回的指针也可以有效地被解引用。


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关键字)来解决。

0

是的,这经常用于返回某些查找的文本部分,例如将一些错误号码翻译成人类友好的字符串。

在以下情况下进行此操作是明智的:

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

如果my_string_to_error()返回一个已分配的字符串,那么在上述(非常)常见的使用情况下,您的程序将会泄漏。
char const *foo_error(...)
{
    return "Mary Poppins";
}

...这样也可以,但有些脑残编译器可能需要你进行强制类型转换。

以这种方式处理字符串,不要返回一本书 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接