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

C 返回指向静态局部变量的指针是否安全?,c,static,C,Static,我正在使用一些广泛使用返回指向静态局部变量指针的习惯用法的代码。例如: char* const GetString() { static char sTest[5]; strcpy(sTest, "Test"); return sTest; } 我认为这是安全的,对吗 PS,我知道这样做会更好: char* const GetString() { return "Test"; } 编辑: 抱歉,函数签名当然应该是: const char* GetString(); 静态变量

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

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}
我认为这是安全的,对吗

PS,我知道这样做会更好:

char* const GetString()
{
  return "Test";
}
编辑: 抱歉,函数签名当然应该是:

const char* GetString();

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

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

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

fprintf(stderr, "Error was %s\n", my_string_to_error(error_code));
如果
my\u string\u to\u error()
返回了一个分配的字符串,鉴于上述(非常)常见的函数用法,您的程序将泄漏

char const *foo_error(...)
{
    return "Mary Poppins";
}
。。。这也没关系,不过有些死气沉沉的编译器可能会希望您使用它


用这种方式观察琴弦,不要还书:)

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

基本上,是的,它是安全的,因为它是静态的,所以该值将无限期地持续

从某种意义上讲,您返回的是指向变量数据的常量指针,而不是指向常量数据的变量指针,这是不安全的。最好不允许调用函数修改数据:

const char *GetString(void)
{
    static char sTest[5];
    strncpy(sTest, "Test", sizeof(sTest)-1);
    sTest[sizeof(sTest)-1] = '\0';
    return sTest;
}
在所示的简单示例中,几乎没有必要担心缓冲区溢出,尽管我的代码版本确实担心,并确保空终止。另一种方法是使用函数
strcpy\s

const char *GetString(void)
{
    static char sTest[5];
    strcpy_s(sTest, sizeof(sTest), "Test");
    return sTest;
}
更重要的是,这两个变量都返回一个指向常量数据的(变量)指针,因此用户不应该修改字符串和(可能)践踏数组范围之外的内容。(正如@strager在评论中指出的,返回
const char*
并不能保证用户不会试图修改返回的数据。但是,他们必须强制转换返回的指针,使其为非const,然后修改数据;这会调用未定义的行为,在这一点上任何事情都是可能的。)

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


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

第一个示例:有些安全

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}
char* const GetString()
{
  return "Test";
}
虽然不建议这样做,但这是安全的,即使函数的作用域结束,静态变量的作用域仍保持活动状态。这个函数根本不是线程安全的。更好的函数可以让您为
GetString()
函数传递一个
char*buffer
和一个
maxsize

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

第二个示例:完全不安全

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}
char* const GetString()
{
  return "Test";
}
如果执行
const char*
,这将是安全的。 你所付出的并不安全。原因是字符串文字可以存储在只读内存段中,允许对其进行修改将导致未定义的结果

char*const
(const指针)表示无法更改指针指向的地址<代码>常量字符*
(指向常量的指针)意味着您无法更改此指针指向的元素

结论:

你也应该考虑一下:

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


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

它取决于您所说的安全。我可以立即看到几个问题:

  • 您返回了一个
    char*const
    ,这将允许调用者更改此位置的字符串。潜在的缓冲区溢出。还是说一个
    常量字符*
  • 您可能在重入或并发方面有问题
  • 解释第二,考虑这个:

    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));
    } 
    
    …将要打印什么


    线程也一样。

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

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

    “一般来说,它们应该避免”可能太强了,但可以肯定的是,您应该意识到风险和限制+1为明确其原因