52

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

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

我认为这是安全的吗?

PS,我知道这将是做同样事情的更好方法:

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

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

const char* GetString();
4

7 回答 7

41

第一个例子:有点安全

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

尽管不推荐,但这是安全的,即使函数范围结束,静态变量的范围仍然有效。这个函数根本不是线程安全的。一个更好的函数会让你传递 achar* buffer和 amaxsizeGetString()填充函数。

特别是,此函数不被视为可重入函数,因为可重入函数不得将地址返回给静态(全局)非常量数据。请参阅可重入函数

第二个例子:完全不安全

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

如果你做了一个const char *. 你给的东西不安全。原因是字符串文字可以存储在只读内存段中,并且允许修改它们会导致未定义的结果。

char* const(const pointer) 意味着您不能更改指针指向的地址。 const char *(pointer to const) 意味着你不能改变这个指针指向的元素。

结论:

您应该考虑:

1)如果您有权访问代码,则修改GetString以采用 a 的参数char* buffer来填充和maxsize使用 a 。

2) 如果您无权访问代码,但必须调用它,请将此方法包装在另一个受互斥锁保护的函数中。新方法如 1 所述。

于 2009-01-17T18:10:41.250 回答
10

static变量(在函数中)就像作用域的全局变量。一般来说,应该避免使用它们(如全局变量,它们会导致重入问题),但有时很有用(一些标准库函数使用它们)。您可以返回指向全局变量的指针,因此也可以返回指向static变量的指针。

于 2009-01-17T18:04:47.330 回答
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));
} 

...要打印什么?

线程也一样。

于 2009-01-17T18:12:48.283 回答
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 在评论中指出的那样,返回 aconst char *并不能保证用户不会尝试修改返回的数据。但是,他们必须将返回的指针转换为非常量,然后再修改数据;这会调用未定义的行为,此时一切皆有可能。)

字面返回的一个优点是不写承诺通常可以由编译器和操作系统强制执行。该字符串将被放置在程序的文本(代码)段中,如果用户试图修改返回值所指向的数据,操作系统将产生错误(Unix 上的段违规)。

[至少有一个其他答案指出该代码不可重入;那是对的。返回文字的版本是可重入的。如果重入很重要,则需要修复接口,以便调用者提供存储数据的空间。]

于 2009-01-17T18:07:38.200 回答
3

是的,它非常安全。局部静态变量的生命周期是整个程序在 C 中执行的生命周期。因此您可以返回一个指向它的指针,因为即使函数返回后数组仍然是活动的,并且返回的指针可以有效地取消引用。

于 2009-01-17T18:06:03.897 回答
1

它非常有用,因为您可以直接将该函数用作 printf 参数。但是,正如前面提到的,在单个调用中多次调用该函数会导致问题,因为该函数使用相同的存储并且调用它两次将覆盖返回的字符串。但我测试了这段代码,它似乎可以工作 - 你可以安全地调用一个函数,其中 givemestring 最多使用 MAX_CALLS 次,它会正常运行。

#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 关键字)

于 2013-03-15T09:47:28.683 回答
0

是的,这经常用于返回某些查找的文本部分,即将某些错误编号转换为人类友好的字符串。

在以下情况下这样做是明智的:

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

如果my_string_to_error()返回一个分配的字符串,你的程序会泄漏,因为这种函数的上述(非常)常见用法。

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

...也可以,但一些脑残的编译器可能希望你强制转换它。

以这种方式观看字符串,不要归还书 :)

于 2009-01-17T18:05:38.717 回答