C中字符串文字的“生命周期”

Posted

技术标签:

【中文标题】C中字符串文字的“生命周期”【英文标题】:"Life-time" of a string literal in C 【发布时间】:2012-04-15 18:01:52 【问题描述】:

难道后面函数返回的指针是不可访问的吗?

char *foo(int rc)

    switch (rc)
    
        case 1:

            return("one");

        case 2:

            return("two");

        default:

            return("whatever");
    

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

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

【问题讨论】:

函数中唯一的“var”是参数int rc。它的生命周期在return-s 中的每一个处结束。您返回的指针是字符串文字。字符串字面量具有静态存储期限:它们的生命周期至少与程序的生命周期一样长。 @PedroAlves 为什么不呢?方法允许抽象;如果将来从翻译资源中读取字符串,但产品的 V1(或 V0.5)不需要国际化支持怎么办? @PedroAlves “您的代码肯定会工作(如果尝试编译,您可以看到它),” 这不符合。许多(大多数?基本上每个?)c 编译器会消耗非法代码,并且经常发出看起来可以工作的代码。但是在另一个编译器(甚至同一个编译器的不同版本)中尝试它可能会失败。 @PedroAlves,一个返回单个常量字符串的函数可能用途有限,但是根据输入或对象状态返回多个常量字符串中的任何一个呢?一个简单的示例是将枚举转换为其字符串表示形式的函数。 显然你从未见过strerror 函数。 【参考方案1】:

是的,局部变量的生命周期在创建它的范围内(,)。

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

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

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


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

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

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

【讨论】:

如果用户返回这样的东西怎么办。字符 *a=&"abc";返回一个;这会无效吗? @Ashwin:字符串文字的类型是char (*)[4]。这是因为,"abc" 的类型是char[4],指向4 个字符的数组的指针被声明为char (*)[4],所以如果你需要获取它的地址,你需要这样做char (*a)[4] = &amp;"abc"; 是的,它是有效的。 @Als "abc" 是 char[4]。 (因为'\0' 也许警告char const s[] = "text"; not 使s 成为字符文字也是一个好主意,因此s i> 在作用域的末尾被销毁,因此任何幸存的指向它的指针都会悬空。 @celtschk:我很乐意,但 Q 专门针对字符串文字所以我会坚持手头的主题。但是,对于对我的回答感兴趣的人,What is the difference between char a[] = “string” and char *p = “string”? 应该会很有帮助.【参考方案2】:

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

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

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

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

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

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

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

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

【讨论】:

仅供参考:这个答案是从***.com/questions/16470959/…合并而来的【参考方案3】:

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

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

【讨论】:

如果用户返回这样的东西怎么办。字符 *a=&"abc";返回一个;这会无效吗? &amp;"abc" 不是char*。它是一个数组地址,它的类型是char(*)[4]。但是,return &amp;"abc";char *a="abc";return a; 都有效。 @asaelr:其实不仅仅是为了为了好看,详情查看我的回答。 @Als 好吧,如果他写了整个程序,他可以避免不写const而改变字符串,这将是完全合法的,但它仍然是不好的风格。 如果对整个程序都有效,为什么还要malloc呢?【参考方案4】:

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

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

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

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

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

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

【讨论】:

仅供参考:这个答案是从***.com/questions/16470959/…合并而来的【参考方案5】:

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

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

另见@asaelr 对const 的正确观察。

【讨论】:

:如果用户返回这样的东西怎么办。字符 *a=&"abc";返回一个;这会无效吗? 对。实际上,可以只写const char *a = "abc";,省略&amp;。原因是双引号字符串解析为其初始字符的地址。【参考方案6】:

局部变量只在它们被声明的范围内有效,但是你没有在那个函数中声明任何局部变量。

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

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

【讨论】:

如果用户返回这样的东西怎么办。字符 *a=&"abc";返回一个;这会无效吗? @Ashwin: &amp;"abc" 不是char* 类型,但是"abc"&amp;"abc" 在程序的整个执行过程中都是有效的。【参考方案7】:

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

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

即使您尝试释放或修改,它也会抛出 segmentation fault 在具有内存保护的平台上

【讨论】:

仅供参考:这个答案是从***.com/questions/16470959/…合并而来的 如果它永远不会悬空,我需要 malloc 吗?没有?【参考方案8】:

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

【讨论】:

如果用户返回这样的东西怎么办。字符 *a=&"abc";返回一个;这会无效吗?【参考方案9】:

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

【讨论】:

以上是关于C中字符串文字的“生命周期”的主要内容,如果未能解决你的问题,请参考以下文章

生命周期如何处理常量字符串/字符串文字?

Fragment生命周期

maven生命周期和插件

C语言中,哪种存储类的作用域与生命周期是不一致的?

vue的生命周期和钩子函数的理解

maven入门maven的生命周期2