C中字符串文字的“生存时间”

问题描述 投票:80回答:9

以下函数返回的指针是否不可访问?

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

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

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

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

c function local-variables string-literals lifetime
9个回答
82
投票

是,局部变量的生存期在创建它的范围({})内。

局部变量具有自动或局部存储。 自动,因为一旦创建范围终止,它们就会被自动销毁。

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

小心点!

但是,请注意,任何试图修改字符串文字内容的尝试都是undefined behavior(UB)。用户程序不允许修改字符串文字的内容。因此,始终建议在声明字符串文字时使用const

const char*p = "string";

而不是

char*p = "string";

实际上,在C ++中,不建议在不使用const的情况下声明字符串文字,但在C语言中则不建议使用。但是,使用const声明字符串文字可为您带来的好处是,编译器通常会在出现警告时提供警告您尝试在第二种情况下修改字符串文字。 

Sample program

#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 ‘&’ operand

字符串文字是l值,即:您可以使用字符串文字的地址,但不能更改其内容。但是,任何其他文字(intfloatchar等)都是r值(C标准对此使用术语

表达式的值),并且不能采用其地址完全没有


[Ref 1]

C99标准6.4.5 / 5“字符串文字-语义”:
在翻译阶段7中,一个或多个字符串文字产生的每个多字节字符序列都附加一个零值的字节或代码。

然后将多字节字符序列用于初始化一个足以包含序列的静态存储持续时间和长度数组

。对于字符串文字,数组元素的类型为char,并使用多字节字符序列的各个字节进行初始化。对于宽字符串文字,数组元素的类型为wchar_t,并使用宽字符序列进行初始化...不确定这些数组是否不同,只要它们的元素具有适当的值。

如果程序尝试修改这样的数组,则行为未定义


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

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

在翻译阶段7中,一个或多个字符串文字产生的每个多字节字符序列都附加一个零值的字节或代码。然后使用多字节字符序列

初始化静态存储持续时间数组和长度足以容纳该序列的长度。

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

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

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

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

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


14
投票
字符串文字在整个程序中都是有效的(并且没有分配堆栈,因此它是有效的。

此外,字符串文字是只读的,因此(为了良好的风格,也许您应该将foo更改为const char *foo(int)


7
投票
是,它是有效的代码,下面的情况1。您至少可以通过以下几种方式安全地从函数返回C字符串:

  • const char*为字符串文字。不能修改,一定不能被调用者释放。由于下面描述的释放问题,对于返回默认值非常有用。如果您实际上需要在某个地方传递函数指针,那么这可能很有意义,因此您需要一个返回字符串的函数。]
  • char*const char*到静态字符缓冲区。一定不能被呼叫者释放。可以修改(可以由调用方(如果不是const的话),也可以由返回它的函数)进行修改,但是返回此函数的函数不能(轻松地)具有多个缓冲区,因此(轻松地)线程安全,并且调用方可能需要复制返回的内容值,然后再次调用该函数。
  • char*分配给malloc的缓冲区。可以修改,但通常必须由调用方显式释放,并且具有堆分配开销。 strdup是这种类型。
  • const char*char*到缓冲区,该缓冲区作为参数传递给函数(返回的指针无需指向参数缓冲区的第一个元素)。将缓冲区/内存管理的职责留给调用者。许多标准的字符串函数都属于这种类型。

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

not free


6
投票
好问题。通常,您是对的,但您的示例例外。编译器为字符串文字静态分配全局内存。因此,函数返回的地址有效。

这是C的一个相当方便的功能,不是吗?它允许函数返回预先编写的消息,而不必强迫程序员担心消息存储在的内存中。

另请参见@asaelr的正确观察const


3
投票
局部变量仅在声明的范围内有效,但是您不会在该函数中声明任何局部变量。

2
投票
str永远不会悬空指针。Because it points to static address字符串文字所在的位置。程序加载时主要是readonlyglobal。即使您尝试释放或修改,它也会抛出segmentation fault

在具有内存保护的平台上


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

0
投票
在上面显示的示例中,实际上是将分配的指针返回给调用上述函数的任何函数。因此它不会成为本地指针。而且,对于需要返回的指针,在全局段中分配了内存。
© www.soinside.com 2019 - 2024. All rights reserved.