C中字符串文字的“生存期”


84

下列函数返回的指针不会不可访问吗?

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

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    }
}

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

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


10
您的函数中唯一的“ var”是parameter int rc。它的生存期在return-s的每一个结束。您要返回的指针是字符串文字。字符串文字具有静态存储期限:它们的生存期至少与程序的生存期一样长。
哈兹2012年

14
@PedroAlves为什么不呢?方法允许抽象;如果将来会从翻译资源中读取字符串,但是产品的V1(或V0.5)不需要国际化支持怎么办?
dlev

1
@PedroAlves “您的代码一定可以正常工作(如果尝试编译,您可以看到它),”这没有遵循。许多(大多数?本质上是每个?)c编译器将使用非法代码,并经常发出看上去有效的代码。但是在另一个编译器(甚至是同一编译器的另一个版本)中尝试它,可能会失败。
dmckee ---前主持人小猫,

6
@PedroAlves,一个返回单个常量字符串的函数可能用途有限,但是根据输入或对象状态返回一个常量字符串中的任何一个的函数呢?一个简单的示例是将枚举转换为其字符串表示形式的函数。
Mark Ransom

4
strerror显然,您从未见过该功能。
卡兹(Kaz)

Answers:


86

是的,局部变量的寿命范围(内{}),其中它被创建。

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

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

请注意!

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

const char*p = "string"; 

代替,

char*p = "string";    

实际上,在C ++中,不建议在C语言中声明一个字符串文字,而在C语言中const却没有。但是,使用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 ‘&’ operand
     

字符串文字是l值,即:您可以使用字符串文字的地址,但不能更改其内容。
然而,任何其它文本(intfloatchar等)的r值(C标准使用术语的表达式的值对这些)和它们的地址不能在所有服用。


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

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

如果它们的元素具有适当的值,则不确定这些数组是否不同。如果程序尝试修改此类数组,则行为未定义


如果用户返回这样的东西怎么办。char * a =&“ abc”; 返回 这会无效吗?
Ashwin

@Ashwin:字符串文字的类型为char (*)[4]。这是因为“ abc”的类型为,char[4]并且指向4个字符的数组的指针被声明为char (*)[4],因此,如果需要获取它的地址,则需要按as进行操作char (*a)[4] = &"abc";,是的,它是有效的。
Alok保存2012年

@Als“ abc”是char[4]。(因为'\0'
asaelr

1
也许这也将是警告说,一个好主意,char const s[] = "text";没有做出s一个字符,并因此s 在范围的结束被破坏,所以任何存活的指针就会悬空。
celtschk 2012年

1
@celtschk:我很乐意,但是Q特别是关于字符串文字的,所以我会坚持手头的话题。但是,对于那些对我的回答感兴趣的人,char a [] =“ string”和char有什么区别? * p =“字符串”?应该会很有帮助。
Alok保存

74

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

对于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是如下定义的字符串的大小;它具有静态存储期限,并使用给定的字符进行初始化。


仅供参考:这个答案是从stackoverflow.com/questions/16470959/…
Shog9

14

字符串文字对于整个程序都是有效的(没有分配而不是堆栈),因此它将是有效的。

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


如果用户返回这样的东西怎么办。char * a =&“ abc”; 返回 这会无效吗?
Ashwin

&"abc"不是char*。它是数组的地址,类型为char(*)[4]。然而,无论是return &"abc";char *a="abc";return a;是有效的。
asaelr 2012年

@asaelr:实际上,这不仅仅是为了获得良好的风格,请查看我的回答以获取详细信息。
Alok保存2012年

@Als好吧,如果他编写了整个程序,他可以避免不编写const而更改字符串,这将是完全合法的,但是它仍然是糟糕的样式。
asaelr 2012年

如果它对整个程序都有效,为什么我们需要对其进行malloc?
TomSawyer

7

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

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

  • char*const char*静态char缓冲区。调用者一定不能释放它。可以对其进行修改(如果不是const,则可以由调用者进行修改,或者可以通过返回它的函数来进行修改),但是返回该函数的函数不能(轻松地)具有多个缓冲区,因此它不是(轻松地)线程安全的,并且调用者可能需要在再次调用该函数之前复制返回的值。

  • char*到分配给的缓冲区malloc。可以对其进行修改,但是通常必须由调用方显式释放它,并且具有堆分配开销。strdup是这种类型的。

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

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


仅供参考:这个答案是从stackoverflow.com/questions/16470959/…
Shog9

6

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

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

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


:如果用户返回这样的东西怎么办?char * a =&“ abc”; 返回 这会无效吗?
Ashwin

对。实际上,可以只写const char *a = "abc";而忽略&。原因是用双引号引起来的字符串解析为其初始字符的地址。
thb 2012年

3

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

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

如果您担心自己所做的事情可能是未定义的无效,则应打开编译器警告,以查看是否确实存在您做错的事情。


如果用户返回这样的东西怎么办。char * a =&“ abc”; 返回 这会无效吗?
Ashwin

@Ashwin:&"abc"不是type char*,但是两者都是"abc"&"abc"并且在整个程序执行期间都有效。
AusCBloke 2012年

2

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

在加载程序时,它将大部分是只读的,并且对于程序是全局的。

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


仅供参考:这个答案是从stackoverflow.com/questions/16470959/…
Shog9

如果它永远不会晃来晃去,我是否需要malloc?没有?
TomSawyer

0

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


如果用户返回这样的东西怎么办。char * a =&“ abc”; 返回 这会无效吗?
Ashwin

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.