一个有关函数中返回本地指针变量的问题。

12

我知道函数中的变量使用堆栈空间。当函数退出时,这些空间被释放。这就是为什么我们应该在函数中将指针变量声明为静态变量的原因。然而,我发现下面的代码也可以正常运行。

gcc版本是:gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)

#include <stdio.h>

char *month_name(int n) {
    char *name[] = {"Invalid name", "Jan.", "Feb", "Mar", "Apr", "May", "June",
                    "July",         "Aug",  "Sep", "Oct", "Nov", "Dec"};

    return n < 1 || n > 12 ? name[0] : name[n];
}

int main() {
    char *month;
    month = month_name(2);
    printf("%s\n", month); // The output is Feb
}

似乎函数中的变量会隐式转换为静态变量。有人能给我解释一下吗?谢谢。


char* name[] 不是一个字符串字面量数组吗?这样写(只是有点不规范)/有效吗?生成的汇编代码似乎表明了这一点。但如果是这样,应该使用 const char* - Lala5th
你返回的不是数组元素的指针,而是数组元素的值(恰好该元素是一个指针,但这并不重要)。 这基本上是与 int f() { int v[] = {0}; return v[0]; } 相同的情况,你知道这并不是问题。 - molbdnilo
2
顺便提一下,由于数组从未更改且您不需要多个副本,因此您应该将数组设置为静态和conststatic char * const name[] = { … };。指向的数据也可以是conststatic const char * const name[] = { … };,如果您更改函数返回类型和调用代码以匹配。 - Eric Postpischil
1
请勿同时标记C和C++,除非询问两种语言之间的差异或交互。基于使用char *来引用字符串字面量而不是C++中所需的const char *,我已删除了C++标签。 - Eric Postpischil
2个回答

19

这里并没有返回本地数据。本地数据是指数组,它包含指向字符串字面量的指针,这些字符串存储在常量只读内存中。它们的位置或生命周期不会改变。因此,返回指向它们的指针是可以的。

然而,如果尝试返回指向数组的指针,那就是错误的。


6
可能需要参考C++标准§2.14.5.8,其中“普通字符串字面值和UTF-8字符串字面值也被称为窄字符串字面值。窄字符串字面值的类型为“n const char的数组”,其中n是以下定义的字符串大小,并具有静态存储期。”和§3.7.1.1,“所有没有动态存储期、线程存储期和不是局部变量的变量都具有静态存储期。这些实体的存储空间应持续整个程序的运行时间。” 可在https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf找到相关内容。 - Tommy Andersen
2
@TommyAndersen 谢谢,那是一份宝贵的参考资料! - Sami Kuhmonen

14
你声明了一个指向字符串字面值(它们的第一个字符)的指针数组。

你声明了一个指向字符串字面值(它们的第一个字符)的指针数组

char *name[] = {"Invalid name", "Jan.", "Feb", "Mar", "Apr", "May", "June",
                "July",         "Aug",  "Sep", "Oct", "Nov", "Dec"};

字符串字面值具有静态存储期,也就是说,在函数退出后它们仍然存在。

例如,在 C 标准(6.4.5 字符串字面值)中写道:

6 在第 7 翻译阶段,对于由一个或多个字符串字面值组成的每个多字节字符序列,都会添加一个值为零的字节或代码。78) 然后使用这些多字节字符序列初始化一个具有静态存储期的数组,该数组的长度刚好足以容纳这个序列....

另一方面,数组本身具有自动存储期,也就是说在函数退出后不再存在。但是该函数返回一个指向字符串字面值的指针,而不是指向数组本身的指针。

如果尝试返回指向数组本身的指针,则该函数将出现错误,例如:

char * ( *month_name(int n) )[13] {
    char *name[] = {"Invalid name", "Jan.", "Feb", "Mar", "Apr", "May", "June",
                    "July",         "Aug",  "Sep", "Oct", "Nov", "Dec"};

    //...     
    return &name;
}

或使用以下方式

char ** month_name(int n) {
    char * name[] = {"Invalid name", "Jan.", "Feb", "Mar", "Apr", "May", "June",
                    "July",         "Aug",  "Sep", "Oct", "Nov", "Dec"};

    return n < 1 || n > 12 ? name : name + n;
}

或者,如果您想声明一个二维数组,可按如下方式:

char *month_name(int n) {
    char name[][13] = {"Invalid name", "Jan.", "Feb", "Mar", "Apr", "May", "June",
                    "July",         "Aug",  "Sep", "Oct", "Nov", "Dec"};

    return n < 1 || n > 12 ? name[0] : name[n];
}

那么在这种情况下,返回语句

return n < 1 || n > 12 ? name[0] : name[n];

由于数组本身在退出函数后不会存在,因此确实会出现未定义的行为。

请注意,在C++中,与C相反,字符串字面值具有常量字符数组的类型。因此,要将您的函数编译为C ++代码,您必须按照以下方式定义该函数。

const char *month_name(int n) {
    const char *name[] = {"Invalid name", "Jan.", "Feb", "Mar", "Apr", "May", "June",
                    "July",         "Aug",  "Sep", "Oct", "Nov", "Dec"};

    return n < 1 || n > 12 ? name[0] : name[n];
}

在C语言中,最好以相同的方式定义函数,因为尽管C语言中字符串文字具有非常量字符数组的类型,但任何试图更改字符串文字的尝试都会引发未定义的行为。这样的函数定义可以避免程序错误。


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