92

下面的函数返回的指针不是不可访问吗?

char *foo(int rc)
{
    switch (rc)
    {
        case 1:

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

所以 C/C++ 中局部变量的生命周期实际上只在函数内,对吗?这意味着,在char* foo(int)终止之后,它返回的指针不再意味着什么,对吧?

我对局部变量的生命周期有点困惑。什么是好的澄清?

4

9 回答 9

92

是的,局部变量的生命周期在创建它的作用域( {, )内。}

局部变量具有自动或本地存储。自动,因为一旦创建它们的范围结束,它们就会自动销毁。

但是,您在这里拥有的是一个字符串文字,它是在实现定义的只读内存中分配的。字符串文字与局部变量不同,它们在整个程序生命周期中保持活动状态。它们具有静态持续时间 [Ref 1]寿命。

一个警告!

但是,请注意,任何修改字符串文字内容的尝试都是未定义行为(UB)。不允许用户程序修改字符串文字的内容。
因此,总是鼓励使用constwhile 声明字符串文字。

const char*p = "string"; 

代替,

char*p = "string";    

事实上,在 C++ 中,不推荐在没有const尽管 C 中声明的情况下声明字符串文字。但是,使用 a 声明字符串文字const给您带来的好处是,如果您尝试修改字符串文字,编译器通常会给您一个警告第二种情况。

示例程序

#include<string.h> 
int main() 
{ 
    char *str1 = "string Literal"; 
    const char *str2 = "string Literal"; 
    char source[]="Sample string"; 
 
    strcpy(str1,source);    // No warning or error just Uundefined Behavior 
    strcpy(str2,source);    // Compiler issues a warning 
 
    return 0; 
} 

输出:

cc1:警告被视为错误
prog.c:在函数'main'中:
prog.c:9:错误:传递'strcpy'的参数1会丢弃指针目标类型的限定符

请注意,编译器会警告第二种情况,但不会警告第一种情况。


在这里回答几个用户提出的问题:

与整数文字有什么关系?

换句话说,以下代码是否有效?

int *foo()
{
    return &(2);
} 

答案是,不,此代码无效。它格式不正确,会产生编译器错误。

就像是:

prog.c:3: error: lvalue required as unary ‘&amp;’ operand
     

字符串文字是左值,即:您可以获取字符串文字的地址,但不能更改其内容。
但是,任何其他文字(intfloatchar等)都是 r 值(C 标准使用术语“表达式的值”来表示这些文字)并且根本无法获取它们的地址。


[参考 1] C99 标准 6.4.5/5“字符串文字 - 语义”:

在翻译阶段 7 中,将一个字节或值为零的代码附加到由一个或多个字符串文字产生的每个多字节字符序列。然后使用多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。对于字符串字面量,数组元素的类型为 char,并使用多字节字符序列的各个字节进行初始化;对于宽字符串文字,数组元素的类型为 wchar_t,并使用宽字符序列进行初始化...

如果它们的元素具有适当的值,则未指定这些数组是否不同。如果程序试图修改这样的数组,则行为是 undefined

于 2012-04-02T02:41:59.123 回答
78

这是有效的。字符串文字具有静态存储持续时间,因此指针不会悬空。

对于 C,这是第 6.4.5 节第 6 段中规定的:

在翻译阶段 7 中,将一个字节或值为零的代码附加到由一个或多个字符串文字产生的每个多字节字符序列。然后使用多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。

对于第 2.14.5 节中的 C++,第 8-11 段:

8 普通字符串文字和 UTF-8 字符串文字也称为窄字符串文字。窄字符串文字的类型为“n 数组const char”,其中 n 是字符串的大小,定义如下,并且具有静态存储持续时间 (3.7)。

9 以 u 开头的字符串文字,例如u"asdf",是char16_t字符串文字。字符串char16_t文字的类型为“n 数组const char16_t”,其中 n 是字符串的大小,定义如下;它具有静态存储持续时间并使用给定字符进行初始化。char16_t单个 c-char 可以以代理对的形式产生多个字符。

10 以 U 开头的字符串文字,例如U"asdf",是char32_t字符串文字。字符串char32_t文字的类型为“n 数组const char32_t”,其中 n 是字符串的大小,定义如下;它具有静态存储持续时间并使用给定字符进行初始化。

11 以 L 开头的字符串文字,例如L"asdf",是宽字符串文字。宽字符串文字的类型为“n 数组const wchar_t”,其中 n 是字符串的大小,定义如下;它具有静态存储持续时间并使用给定字符进行初始化。

于 2013-05-09T20:56:15.000 回答
14

字符串文字对整个程序都有效(并且不是分配给堆栈),所以它是有效的。

此外,字符串文字是只读的,所以(为了好的风格)也许你应该foo改为const char *foo(int)

于 2012-04-02T02:42:27.050 回答
7

是的,它是有效代码,请参见下面的案例 1。至少可以通过以下方式从函数中安全地返回 C 字符串:

  • const char*到字符串文字。它不能被修改,也不能被调用者释放。由于下面描述的释放问题,它很少用于返回默认值。如果您确实需要在某处传递函数指针,这可能是有意义的,因此您需要一个返回字符串的函数..

  • char*const char*静态字符缓冲区。它不能被调用者释放。它可以被修改(如果不是 const,则由调用者或返回它的函数),但返回 this 的函数不能(容易)有多个缓冲区,因此它不是(容易)线程安全的,调用者可能需要在再次调用函数之前复制返回的值。

  • char*到分配有的缓冲区malloc。它可以被修改,但它通常必须由调用者显式释放并且具有堆分配开销。strdup属于这种类型。

  • const char*char*作为参数传递给函数的缓冲区(返回的指针不需要指向参数缓冲区的第一个元素)。它将缓冲区/内存管理的责任留给调用者。许多标准字符串函数都属于这种类型。

一个问题是,将这些混合在一个函数中会变得复杂。调用者需要知道它应该如何处理返回的指针,它的有效时间,以及调用者是否应该释放它,并且在运行时没有(好的)方法来确定它。因此,例如,您不能有一个函数,该函数有时返回指向调用者需要的堆分配缓冲区的指针,有时返回指向free字符串文字中的默认值的指针,而调用者不得 free

于 2013-05-09T21:22:19.447 回答
6

好问题。一般来说,你是对的,但你的例子是例外。编译器为字符串文字静态分配全局内存。因此,您的函数返回的地址是有效的。

这就是 C 的一个相当方便的特性,不是吗?它允许函数返回预先编写的消息,而无需程序员担心存储消息的内存。

另请参阅@asaelr 的正确观察 re const

于 2012-04-02T02:42:07.673 回答
3

局部变量仅在它们声明的范围内有效,但是您没有在该函数中声明任何局部变量。

从函数返回指向字符串文字的指针是完全有效的,因为字符串文字存在于程序的整个执行过程中,就像一个static或一个全局变量一样。

如果你担心你正在做的事情可能是无效的未定义,你应该打开你的编译器警告,看看你是否真的做错了什么。

于 2012-04-02T02:44:25.463 回答
2

str永远不会是悬空指针,因为它指向字符串文字所在的静态地址。

当它被加载时,它将主要是只读的并且对程序是全局的。

即使您尝试释放或修改,它也会在具有内存保护的平台上引发分段错误

于 2013-05-10T06:02:52.040 回答
0

在堆栈上分配了一个局部变量。函数完成后,变量超出范围,不再可在代码中访问。但是,如果您有一个全局(或简单地 - 尚未超出范围)指针指向该变量,它将指向堆栈中该变量所在的位置。它可能是另一个函数使用的值,也可能是无意义的值。

于 2012-04-02T02:44:43.340 回答
0

在您显示的上述示例中,您实际上将分配的指针返回到调用上述函数的任何函数。所以它不会成为本地指针。而且,对于需要返回的指针,内存分配在全局段中。

于 2012-04-02T07:08:05.840 回答