"Life-time" C 语言中的字符串文本



以下函数返回的指针不是无法访问吗?

char *foo(int rc)
{
    switch (rc)
    {
        case 1:
            return("one");
        case 2:
            return("two");
        default:
            return("whatever");
    }
}

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

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

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

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

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

警告一句!

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

const char*p = "string"; 

而不是

char*p = "string";    

事实上,在C++中,不推荐声明一个没有const的字符串文字,尽管不是在 C 中。但是,使用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 Undefined 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标准使用这些术语表达式的值(,它们的地址根本不能被获取。

<小时 />

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

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

如果这些数组

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

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

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

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

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

8 普通字符串文本

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

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

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

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

字符串文字对整个程序有效(并且不分配堆栈(,因此它将有效。

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

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

  • const char*为字符串文本。它不能被修改,并且不得被调用方释放。它很少用于返回默认值,因为下面描述的释放问题。如果你真的需要在某处传递一个函数指针,这可能是有意义的,所以你需要一个返回字符串的函数。

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

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

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

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

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

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

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

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

从函数返回指向字符串文本的

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

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

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

加载

程序时,它将主要是只读全局的。

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

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

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

最新更新