【问题标题】:Scope of char * compared with char array in Cchar * 的范围与 C 中的 char 数组比较
【发布时间】:2020-10-07 12:24:28
【问题描述】:

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

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 上的 cmets 和/或指向 C99 或更新标准的引用指向了区分这两种形式也将不胜感激。

【问题讨论】:

  • 提供的代码如何作为指针和数组驻留在不同存储/属于不同范围的示例?您是一位经验丰富的 C 用户。这些担忧是如何产生的?
  • @RobertSsupportsMonicaCellio - 通过观察,您的第二条评论。当在main() 中的rintf() 语句中调用时,指针始终超出函数的生命周期,而char [] 则不然。这就是为什么我追求这一点的原因,并且对 UB 是否参与我的观察感兴趣。关于“你是一个有经验的 C 用户。这些问题是怎么来的”,LOL,我有一些经验,但距离成为一名完美的 C 程序员还有很长的路要走。跨度>
  • "指针始终存在于函数的生命周期之外..." - 不,它没有。返回的只是字符串文字的第一个元素的地址,它本身一直存在到程序终止。这并不意味着strN 的对象本身还活着。
  • @RobertSsupportsMonicaCellio - 明确区分。谢谢。 (您的一些观察将添加到这里的答案内容)
  • 我不太明白你到底问了什么,因为我很困惑,想知道你的意思是什么。这就是我没有回答的原因。我想你可能会发现一些邪恶的记忆黑客。

标签: c scope


【解决方案1】:

这个:

char str1[] = "int_1 < int_2";

定义一个用给定字符串字面量初始化的数组。如果您返回str1,因为数组名称衰减为指向其第一个元素的指针,您将返回一个指向局部变量的指针。当函数返回时,该变量的生命周期结束,并尝试随后使用该地址调用undefined behavior

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

对象的生命周期是程序的一部分 保证保留存储的执行 为了它。一个对象存在,有一个常量地址,并且保留 其在其整个生命周期中的最后存储价值。 如果一个对象 在其生命周期之外被引用,行为是 undefined. 指针的值在 它指向(或刚刚过去)的对象达到其生命周期的终点。

相比之下,这个:

char *str1 = "int_1 < int_2";  

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

字符串字面量的生命周期在C standard 的第 6.4.5p6 节中指定:

在翻译阶段 7 中,一个字节或值为零的代码是 附加到每个多字节字符序列中 字符串文字或文字。多字节字符序列是 然后用于初始化一个静态存储时长数组 和长度刚好足以包含序列。

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

一个没有声明标识符的对象
存储类说明符_Thread_local,或者使用 外部或内部链接或使用存储类说明符static, 具有静态存储持续时间它的生命周期是整个 程序的执行及其存储的值仅被初始化 一次,在程序启动之前。

【讨论】:

    【解决方案2】:

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

    char str1[] = "int_1 < int_2";
    
    char *str1 = "int_1 < int_2";
    

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

    即数组和指针本身占用的内存在退出函数后将无效。例如,它可以被覆盖。

    不同之处在于指针str1 指向具有静态存储持续时间的字符串文字。所以你可以从函数中返回指针,因为字符串字面量是活动的并且返回的指针会指向它。

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

    【讨论】:

    • 这两个陈述似乎彼此不一致:两个标识符具有相同的函数作用域,并且在函数之外不存在。并且指针str1指向具有静态存储持续时间的字符串文字static 不代表程序的生命周期吗?
    • @ryyker 表示指针的标识符具有自动存储持续时间。该函数返回它的副本。但是字符串文字具有静态存储持续时间。所以返回值是有效的,因为它是一个存活对象的地址。
    • 那你不是真的想说两个标识符具有相同的函数作用域,并且在函数之外不存在
    • @ryyker 你错了。指针和数组这两个对象是具有自动存储持续时间的功能块范围的局部变量。
    • @ryyker 所有具有自动存储持续时间的局部变量的生命周期在退出其范围后结束。您示例中的指针和数组具有相同的范围。所以在退出函数后,它们就不再存在了。所以他们在这方面没有区别。
    【解决方案3】:

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

    取而代之的是指针的值——实际上是字符串字面量的第一个元素的地址,这里是 f.e. "int_1 &lt; int_2",分配给它 - 返回按值,而不是指针本身按引用

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


    事实上,指向char (char *) 的指针和char (char[]) 的数组都具有相同的存储类auto,并且仅对函数compare_int 可见(具有函数局部范围)。

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

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

    字符串文字没有绑定到特定的指针。

    它们是否已经被存储类说明符static 限定了,那么它们的对象将一直存在于内存中直到程序终止,通过不同的函数调用保留它们的值,并且在你通过以下方式引用它们的实际对象的任何地方都可见在调用者中传递指向它们的指针。

    但即便如此,返回的指针也不是对指针本身的引用,它的值——字符串字面量的第一个元素的地址——是由值返回的。


    如果您将被调用函数中的指针视为“持有人”,或者甚至更好地视为“送货人”,例如友好地从亚马逊运送您的货物的人,您可以想象得更好。他/她只持有该地址一段时间,但此后他/她将价值提供给另一个人。

    compare_int 返回地址值时也会发生类似的情况。被调用函数strN 中的指针将地址值提供给调用者。在那里它被当作printf()的参数。

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:

        所以,我在 CodeBlocks IDE 中执行了您的代码,结果如下:-

        案例 1

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

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

        案例 0

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

        结论:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-27
          • 2013-02-09
          • 2019-11-08
          • 2021-12-28
          • 2022-01-19
          • 1970-01-01
          相关资源
          最近更新 更多