char * 的范围与 C 中的 char 数组相比

Scope of char * compared with char array in C

同一变量的这两种形式,当在功能块的范围内定义时,我认为应该具有相同的范围,即在定义它们的功能块{...}中:

char str1[] = "int_1 < int_2";

char *str1 = "int_1 < int_2";  

但我的观察是 char * 超出了功能范围,而 char [] 不复存在。在这两种情况下,符号名称 str1 都指向创建变量的内存位置,那么为什么一个 似乎 存在于函数之外,而另一个却没有?以下代码可用于测试此行为:(将 #define0 更改为 1 选择一种形式而不是另一种形式以供说明。)

另请注意,虽然 static 修饰符可用于修改作用域,但这里有意不使用它来观察没有它的行为。

#define DO (1)  //define as either 1 or 0

char * compare_int(int x1, int x2);

int main(void)
{
    int a = 0;
    int b = 0;
    int c = '\n';

    srand(clock()/CLOCKS_PER_SEC);

    while(c != 'q')
    {
        a = rand()%3;
        b = rand()%3;
        printf("%s\n( enter 'q' to exit. )\n\n", compare_int(a, b));
        c = getchar();
    }
    return 0;
}

char * compare_int(int x, int y) 
{
    printf("%d    %d\n", x, y);

#if(DO)
    char str1[] = "int_1 < int_2";
    char str2[] = "int_1 == int_2";    
    char str3[] = "int_1 > int_2";
#else
    char *str1 = "int_1 < int_2";
    char *str2 = "int_1 == int_2";    
    char *str3 = "int_1 > int_2";
#endif  

    return x < y ? (str1) : x == y ? (str2) : (str3);

}

我已阅读 this,它确实回答了这个问题的一些关键部分,但对我代码中的任何 UB 进行了评论,and/or 引用了 C99 或指向段落的更新标准) 区分这两种形式,我们也将不胜感激。

在函数内具有自动存储持续时间的这些声明中

char str1[] = "int_1 < int_2";

char *str1 = "int_1 < int_2";

这两个标识符具有相同的函数作用域,并且在函数之外不存在。

也就是数组占用的内存和指针本身占用的内存在函数退出后就失效了。例如它可以被覆盖。

不同之处在于指针str1指向一个具有静态存储持续时间的字符串文字。所以你可以 return 来自函数的指针,因为字符串文字将是活动的并且 returned 指针将指向它。

至于数组str1则由字符串字面量初始化(通过将字符串字面量的元素复制到它自己的元素中),但它本身具有自动存储持续时间。因此,您不能将数组指示符用作 return 表达式,因为 returned 指针将无效,因为数组在退出函数后将不再存在。

这个:

char str1[] = "int_1 < int_2";

定义一个用给定字符串文字初始化的数组。如果你 return str1,因为数组名称衰减到指向其第一个元素的指针,你正在 return 指向局部变量的指针。该变量的生命周期在函数 returns 时结束,并试图随后使用该地址调用 undefined behavior.

这在 C standard 的第 6.2.4p2 节中有记录:

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

相比之下,这个:

char *str1 = "int_1 < int_2";  

定义一个指针,它用字符串文字的地址初始化。字符串常量具有完整的程序生命周期,因此读取指向一个的指针是安全的。在这种情况下,当你 return str1 时,你 returning str1value (不是它的地址)是字符串文字的地址。

字符串文字的生命周期在 C standard:

的第 6.4.5p6 节中指定

In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals. The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence.

静态存储时长定义在6.2.4p3:

An object whose identifier is declared without
the storage-class specifier _Thread_local, and either with external or internal linkage or with the storage-class specifier static, has static storage duration. Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.

使用 char[] 时,您的局部变量 - 分配在堆栈上 - 是一个字符数组。你从函数中 return 得到的是指向这个局部变量的指针。然而,一旦你从函数中 return 并且你的指针 return 一直指向堆栈上的某个位置,该位置迟早会被其他东西覆盖,局部变量就会消失。

使用 char * 你的局部变量只是一个指针,指向一个永远存在的常量。你return复制这个指针,完全没问题。

返回指向堆栈上局部变量的指针是一种会让您发疯的错误,因为首先 returned 字符串可能看起来没问题,但一段时间后它会随机出现将被覆盖。在创建字符串时,您将继续调试您怀疑会破坏字符串的代码,而没有意识到错误是在更早的时候设置的。

如果您 return 来自被调用函数的指针,您不会 return 对指针本身的 引用

相反,指针的值 - 实际上是字符串文字的第一个元素的地址,这里 f.e。 "int_1 < int_2",分配给它 - returned by value,而不是指针本身 by reference.

程序终止之前,字符串文字本身驻留在只读内存中。


实际上,指向charchar *)的指针和charchar[])的数组具有相同的存储空间class auto 并且仅对函数 compare_int 可见(具有函数局部作用域)。

函数执行一次后,它们都不再存在(在内存中),因此也不再可见。

printf()调用中使用的值实际上是通过值传递的字符串字面量的第一个元素的地址。与被调用函数中的指针无关,这里strN.

字符串文字未绑定到特定指针。

如果它们使用存储-class 说明符 static 进行限定,那么它们的对象将一直存在于内存中直到程序终止,通过不同的函数调用保留它们的值并且在您随处可见通过在调用者中传递指向它们的指针获得了对它们实际对象的引用。

但即便如此,returned 指针并不是对指针本身的引用,它的值——字符串文字的第一个元素的地址——是按值returned 的。


如果您将被调用函数中的指针视为 "holder" 或更好的 "delivery person",您可以想象得更好,就像友好地从 Amazon 送货的指针一样。 S/he 仅在一定时间内持有地址,但此后 s/he 将值提供给另一个人。

这与 return 从 compare_int 中获取地址值时类似。被调用函数 strN 中的指针将地址值提供给调用者。在那里它被当作 printf().

的参数

所以,我在 CodeBlocks IDE 中执行了你的代码,这是我发现的:-

案例 1

compare_int(int x, int y)中,当x = 2 & y = 0,在str1, str2, and str3 character arrays(char [])声明之前,str1, 2 & 3包含一些垃圾值,然后在某些内存地址的函数中局部声明它们,并为它们分配值。

然后根据条件,address of str3成功返回给调用函数,也就是一个char *到那个地址。但是当 printf("%s\n( enter 'q' to exit. )\n\n", compare_int(a, b)); printf() 试图通过 compare_int() 函数返回的 char * 读取该地址时,它会在那里找到一些垃圾值。这意味着 str1[], str2[], and str3[] 数组的值是该函数的局部值,并且不会在 complex_int() 函数之外持续存在。

案例 0

str1, str2, and str3声明并定义为constant strings时,它们的值由函数返回并被printf()成功读取,这意味着它们的值在不同的函数调用之间保持不变。

结论:

根据这种行为,我可以说在 case 0 中,string literals 存储在 permanent storage area 和本地 char * 中存储在stack指向那个string literal,当我们return这个char *,它returns它的值是那个string literal的地址这就是它打印成功的原因。另一方面,在 case 1 中,char [] 存储在 stack 中并且在 parent function 中是本地的,并且它们的值不会在两者之间持续存在不同的函数调用。这就是为什么无法从 main().

访问它们的原因