【问题标题】:Confusion about how char *s and char s[] works at low level关于 char *s 和 char s[] 如何在低级别工作的困惑
【发布时间】:2020-05-07 10:06:57
【问题描述】:

我知道类似的问题,例如this question,已在此处发布和回答,但这些答案并没有为我提供完整的图片,因此我将其作为一个新问题发布。希望没关系。

看下面的sn-ps-

char s[9] = "foobar";  //ok
s[1] = 'z'             //also ok

char s[9];
s = "foobar"   //doesn't work. Why? 

但请看以下案例-

char *s = "foobar";      //works
s[1] = 'z';              //doesn't work
char *s;
s = "foobar";            //unlike arrays, works here

这有点令人困惑。我的意思是我有模糊的理解,我们不能为数组赋值。但是我们可以修改它。对于char *s,似乎我们可以赋值但不能修改它,因为它是写在只读存储器中的。但我仍然无法获得全貌。

底层到底发生了什么?

【问题讨论】:

  • 如果可能的话......想象一下char s[9]; s = "123"; s[0] = 'a';
  • 想象一下,对于每个字符串文字(例如"foobar"),编译器会自动将const char string_literal_001[] = {'f', 'o', 'o', 'b', 'a', 'r', '\0'}; 之类的定义放在其他地方,而您实际上所做的是s = string_literal_001;简单赋值或strcpy(s, string_literal_001) 进行初始化。

标签: c arrays string pointers char


【解决方案1】:

char s[9] = "foobar"; 这是初始化。声明一个大小为 9 的字符数组,然后其内容接收字符串 "foobar",其余字符设置为 '\0'

s = "foobar" 只是无效的 C 语法。您不能将字符串分配给 char 数组。使s 具有foobar 的值。使用strcpy(s,"foobar");

char *s = "foobar"; 也是初始化,但是,这会将常量字符串 foobar地址 分配给 指针变量 s。请注意,我说的是“常量字符串”。字符串文字在大多数平台上都是常量。一个更好的表达方式是写const char *s = "foobar";

确实,您的下一个分配s[1]= 'z'; 将不起作用,因为s 是不变的。

【讨论】:

  • 我想我需要更好地理解“初始化”和“赋值”的含义。至于s = "foobar"是无效语法,编译器实际上抱怨error: array type 'char [9]' is not assignable s = "foobar"
  • 每当你声明一个变量,你就可以初始化它。那是编译时。 = 的任何其他用途都是运行时的 assignment
  • 您或其他人能否扩展“您不能将字符串分配给 char 数组”。部分?
  • 这很有帮助。我真的没有考虑那部分。
  • “将字符串赋值给char数组”是指字符串的每一个char都必须赋值给数组对应的char。所有这些指令不是由 C 编译器自动生成的。然而,要做到这一点,有库函数strcpy
【解决方案2】:

您需要了解这些表达式实际上在做什么,然后您可能会清楚。

  1. char s[9] = "foobar"; -> 初始化char数组s由字符串文字"foobar"。正确。

  2. s[1] = 'z' -> 将字符常量 'z' 分配给第二个元素。 char 数组 s。正确。

  3. char s[9]; s = "foobar"; -> 声明char 数组a,然后尝试将字符串文字"foobar" 分配char 数组。不允许。您实际上不能在 C 中分配数组,您只能在定义数组本身时初始化带有字符串的 char 数组。这就是区别。如果要将字符串复制到 char 的数组中,请改用 strcpy(s, "foobar");

  4. char *s = "foobar"; -> 定义指向char s 的指针并将其初始化为指向字符串文字"foobar"。正确。

  5. s[1] = 'z'; -> 尝试修改字符串文字"foobar",即s指向的。不允许。字符串文字存储在只读内存中。

  6. char *s; s = "foobar"; -> 声明指向char s 的指针。然后分配指针指向字符串文字"foobar"。正确。

【讨论】:

    【解决方案3】:

    这用一个初始化器声明数组s

    char s[9] = "foobar";  //ok
    

    但这是一个无效的赋值表达式,左侧有数组s

    s = "foobar";   //doesn't work. Why?
    

    赋值表达式和带有初始化器的声明在语法上不是一回事,尽管它们都在语法中使用=

    对数组s的赋值不起作用的原因是数组衰减为指向其表达式中第一个元素的指针,所以赋值等价于:

    &(s[0]) = "foobar";
    

    赋值表达式要求左侧有一个左值,但& 地址运算符的结果不是左值。尽管数组s 本身是一个左值,但表达式会将其转换为不是左值 的东西。因此,数组不能用于赋值表达式的左侧。


    以下:

    char *s = "foobar";      //works
    

    字符串文字"foobar" 存储为char 的匿名数组,并作为初始化器 衰减为指向其第一个元素的指针。所以上面等价于:

    char *s = &(("foobar")[0]);      //works
    

    初始化器的类型与s (char *) 相同,所以没问题。

    对于后续的赋值:

    s[1] = 'z';              //doesn't work
    

    语法上是正确的,但它违反了约束,导致未定义的行为。被违反的约束是由字符串字面量创建的匿名数组是不可修改的。对这样一个数组的元素赋值是一种修改,是不允许的。

    随后的赋值:

    s = "foobar";            //unlike arrays, works here
    

    相当于:

    s = &(("foobar")[0]);            //unlike arrays, works here
    

    它将char * 值分配给char * 类型的变量,所以没问题。


    对比下面对初始化器"foobar"的使用:

    char *s = "foobar";      //works
    

    在前面的声明中使用它:

    char s[9] = "foobar";  //ok
    

    有一个特殊的初始化规则允许char 的数组由可选用大括号括起来的字符串字面量进行初始化。该初始化规则用于初始化char s[9]

    用于初始化数组的字符串字面量也创建了一个匿名数组char(至少名义上如此),但无法访问该匿名数组char,因此它可能会从编译器。这与用于初始化char *s 的字符串文字创建的char 匿名数组形成对比,可以通过s 访问。

    【讨论】:

      【解决方案4】:

      将 C 视为不允许您对数组做任何事情可能会有所帮助,除了在一些特殊情况下提供帮助。 C 起源于编程语言只是帮助您移动单个字节和“单词”(2 或 4 个字节)并使用它们进行简单的算术和运算。考虑到这一点,让我们看看你的例子:

      char s[9] = "foobar"; //ok

      这是一种特殊情况:当你定义一个字符数组时,编译器会帮你初始化它。在定义中,你可以提供一个字符串字面量,它代表一个字符数组,编译器会用字符串字面量的内容初始化你的数组。

      s[1] = 'z' //also ok

      是的,这只是将一个字符的值移动到一个数组元素中。

      char s[9]; s = "foobar" //doesn't work. Why?

      这不起作用,因为这里没有帮助。 s"foobar" 都是数组,但 C 没有规定将数组作为一个完整的对象处理。

      然而,尽管 C 并不将数组作为一个整体对象来处理,但它确实为处理数组提供了一些帮助。由于编译器不能处理整个数组,程序员需要一些其他的方法来处理数组。所以 C 被赋予了一个特性,当你在表达式中使用数组时,编译器会自动将它转换为指向数组第一个元素的指针,这将有助于程序员编写代码来处理数组元素。我们在您的下一个示例中看到了这一点:

      char *s = "foobar"; //works

      char *ss 声明为指向char 的指针。接下来,字符串文字"foobar" 表示一个数组。上面,我们看到使用字符串字面量来初始化数组是一种特殊情况。但是,这里的字符串文字不用于初始化数组。它用于初始化指针,因此不适用特殊情况规则。在这种情况下,由字符串字面量表示的数组会自动转换为指向其第一个元素的指针。因此s 被初始化为指向包含“f”、“o”、“o”、“b”、“a”、“r”和空字符的数组的第一个元素的指针。

      s[1] = 'z'; //doesn't work

      字符串字面量定义的数组是常量。它们是“只读的”,因为 C 标准没有定义当您尝试修改它们时会发生什么。在许多 C 实现中,它们被分配给只读内存,因为操作系统和计算机硬件不允许通过正常程序方式对其进行写入。所以s[1] = 'z'; 可能会从编译器得到异常(陷阱)或警告或错误消息。 (理想情况下,char *s = "foobar"; 将被禁止,因为 "foobar" 作为常量,其类型为 const char [7]。但是,因为 const 在早期 C 中不存在,字符串文字的类型没有 const。 )

      char *s; s = "foobar"; //unlike arrays, works here

      这里s是一个char *,字符串文字"foobar"会自动转换成指向它的第一个元素的指针,而那个指针是char *,所以赋值没问题。

      【讨论】:

        猜你喜欢
        • 2010-12-14
        相关资源
        最近更新 更多