【问题标题】:Is returning a pointer to a static local variable safe?返回指向静态局部变量的指针是否安全?
【发布时间】:2025-12-26 00:55:12
【问题描述】:

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

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

我认为这是安全的吗?

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

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

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

const char* GetString();

【问题讨论】:

    标签: c static


    【解决方案1】:

    第一个例子:有点安全

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

    虽然不推荐,但这是安全的,静态变量的作用域即使在函数作用域结束时仍然有效。这个函数根本不是线程安全的。一个更好的函数会让你传递一个char* buffer 和一个maxsize 来填充GetString() 函数。

    特别是,此函数不被视为可重入函数,因为可重入函数不得将地址返回到静态(全局)非常量数据 .见reentrant functions

    第二个例子:完全不安全

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

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

    char* const(常量指针)意味着你不能改变指针指向的地址。 const char *(指向 const 的指针)表示您无法更改此指针所指向的元素。

    结论:

    你应该考虑:

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

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

    【讨论】:

      【解决方案2】:

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

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

      ...要打印什么?

      线程也是如此。

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • “一般情况下应该避免它们”可能过于强烈,但您肯定应该意识到风险和限制。 +1 以明确为什么 可以。
        • 我同意 dmckee 的观点,重入问题是因为静态设计在函数调用中仍然有效。这不是坏行为。但您确实应该意识到风险。
        【解决方案4】:

        从根本上说,是的,从某种意义上说,它是安全的,因为它是静态的,因此该值将无限期地持续下去。

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

        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 在 cmets 中指出的那样,返回 const char * 并不能保证用户不会尝试修改返回的数据。但是,他们必须强制转换返回的指针,使其成为非常量然后修改数据;这会调用未定义的行为,此时一切皆有可能。)

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

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

        【讨论】:

        • 这不是一个承诺:这是一个建议。你可以去掉 const。你是对的,它可能应该是 const char * 而不是 char *const,但我不确定函数返回值的含义是否不同。
        • @strager:是的,您可以强制编译器滥用返回。在 'char *const' 中,const 没有任何好处;一旦将值分配给单独的(非常量)指针变量,就可以修改该指针;返回值永远不能被修改。
        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          它非常有用,因为您可以将函数直接用作 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 关键字)解决

          【讨论】:

            【解决方案7】:

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

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

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

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

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

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

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

            【讨论】: