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

时间:2009-01-17 17:52:00

标签: c static

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

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

我认为这是安全的吗?

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

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

修改 道歉,功能签名当然应该是:

const char* GetString();

7 个答案:

答案 0 :(得分:36)

第一个例子:有点安全

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

虽然不推荐,但这是安全的,即使函数的范围结束,静态变量的范围仍然存在。这个功能根本不是线程安全的。一个更好的函数可以让你传递char* buffermaxsize来填充GetString()函数。

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

第二个例子:完全不安全

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

如果您执行了const char *,这将是安全的。 你给的是不安全的。原因是因为字符串文字可以存储在只读存储器段中并允许它们被修改将导致不确定的结果。

char* const(const指针)意味着您无法更改指针指向的地址。 const char *(指向const的指针)意味着您无法更改此指针指向的元素。

结论:

你应该考虑:

1)如果您有权访问该代码,请修改GetString以使char* buffer的参数填充并使用maxsize

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

答案 1 :(得分: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;
}

更重要的是,两个变体都返回一个(变量)指针指向常量数据,因此用户不应该修改字符串并且(可能)在数组范围之外进行践踏。 (正如@strager在评论中指出的那样,返回const char *并不能保证用户不会尝试修改返回的数据。但是,他们必须转换返回的指针,使其成为非常量的然后修改数据;这会调用未定义的行为,此时可能发生任何事情。)

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

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

答案 2 :(得分:8)

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

答案 3 :(得分:8)

这取决于您的安全意味着什么。我可以立即看到几个问题:

  1. 您已返回char * const,这将允许呼叫者更改此位置的字符串。潜在的缓冲区溢出。或者你的意思是const char *
  2. 您可能在重入或并发方面遇到问题。
  3. 要解释第二个,请考虑一下:

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

    ......会打印什么?

    线程相同。

答案 4 :(得分:2)

是的,这是非常安全的。本地静态的生命周期是C中整个程序执行的生命周期。因此,您可以返回指向它的指针,因为即使在函数返回后数组也将处于活动状态,并且返回的指针可以有效地解除引用。

答案 5 :(得分: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关键字)来解决

答案 6 :(得分:0)

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

在您执行此操作的情况下执行此操作是明智的:

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

如果my_string_to_error()返回了一个已分配的字符串,那么鉴于上述(非常)常用的此类函数,您的程序将会泄漏。

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

...也没关系,一些脑死亡的编译器可能会要你投射它。

只要以这种方式观看字符串,就不要回书:)