【问题标题】:What happens when a char array gets initialized from a string literal?当 char 数组从字符串字面量初始化时会发生什么?
【发布时间】:2018-06-19 22:59:10
【问题描述】:

据我了解,以下代码的工作方式如下:

char* cptr = "Hello World";

“Hello World”位于程序内存的.rodata 部分。字符串字面量"Hello World" 返回一个指向字符串基地址的指针,或者所谓的“数组”中第一个元素的地址,因为字符在内存中按顺序排列,所以它是“H”。这是我的小图,因为我可视化了存储在内存中的字符串文字:

0x4 : 'H'
0x5 : 'e'
0x6 : 'l'
0x6 : 'l'
0x7 : 'o'
0x8 : ' '
0x9 : 'W'
0xa : 'o'
0xb : 'r'
0xc : 'l'
0xd : 'd'
0xe : '\0'

所以上面的声明变成了:

char* cptr = 0x4;

现在 cptr 指向字符串文字。我只是在编地址。

0xa1 : 0x4

现在这段代码是如何工作的?

char cString[] = "Hello World";

我假设和之前的情况一样,"Hello World" 也降级为 'H' 和 0x4 的地址。

char cString[] = 0x4;

= 与 char 数组的初始化一起使用时,我将其作为重载赋值运算符阅读。据我了解,仅在初始化 C 字符串时,它会从给定的基地址开始逐个字符地复制到 C 字符串中,直到它作为最后一个复制的字符命中 '\0' 为止。它还为所有字符分配足够的内存。因为重载的运算符实际上只是函数,所以我假设它的内部实现类似于strcpy()

我希望一位更有经验的 C 程序员确认我对这段代码如何工作的假设。这是我在将字符串文字中的字符复制到其中之后对 C 字符串的可视化:

0xb4 : 'H'
0xb5 : 'e'
0xb6 : 'l'
0xb6 : 'l'
0xb7 : 'o'
0xb8 : ' '
0xb9 : 'W'
0xba : 'o'
0xbb : 'r'
0xbc : 'l'
0xbd : 'd'
0xbe : '\0'

再一次,地址是任意的,关键是堆栈中的 C 字符串与内存中 .rodata 部分中的字符串文字不同。

我想做什么?我正在尝试使用 char 指针来临时保存字符串文字的基地址,并使用相同的 char 指针(字符串文字的基地址)来初始化 C 字符串。

char* cptr = "Hello World";
char cString[] = cptr;

我假设"Hello World" 的计算结果是它的基地址0x4。所以这段代码应该是这样的:

char* cptr = 0x4;
char cString[] = 0x4;

我认为它应该与char cString[] = "Hello World"; 没有什么不同,因为“Hello World”的计算结果是它的基地址,这就是存储在 char 指针中的内容!

但是,gcc 给了我一个错误:

error: invalid initializer
char cString[] = cptr;
                 ^
  1. 为什么不能使用 char 指针作为临时占位符来存储字符串文字的基地址?
  2. 此代码如何工作?我的假设是否正确?
  3. 在代码中使用字符串文字是否会将基地址返回到将字符存储在内存中的“数组”?

【问题讨论】:

  • 数组不是指针。在 SO 和网络上的其他地方有很多很多对此的解释。是的,您可以分配一个指针,使其指向一个数组。不,您不能分配一个数组以使其指向任何东西(因为它不是指针)。初始化 char cString[] = "Hello World" 不涉及任何指针——符号 cString 最终是一个 12 个字符的数组的地址,使用“Hello World”中的字符进行初始化。
  • 你写“所谓的'数组'”好像有什么可疑之处。那没有。术语“数组”在 C 中定义良好,标准明确规定将 C 源文件翻译成程序的部分过程是在每个字符串字面量中添加一个空字节,并使用生成的字节序列来初始化静态数组的长度足以容纳它们。没有“好像”或“喜欢”——出现在 C 源代码中的字符串文字对应于程序中的 真正 数组。
  • 我不懂“善意”。我的观点是字符串文字似乎是存储在程序的 .rodata 部分中的数组。但是,它不是一个数组,因为我,程序员,会在代码中显式声明它:char array[];

标签: c pointers literals c-strings


【解决方案1】:

您对内存布局的理解或多或少是正确的。但是您遇到的问题是 C 中的初始化语义之一。

此处声明中的= 符号不是赋值运算符。相反,它是为被实例化的变量指定初始化器的语法。一般情况下,T x = y;T x; x = y; 不同。

有一个语言规则,字符数组可以从字符串字面量初始化。 (在这种情况下,字符串文字不是“评估为它的基地址”)。 没有语言规则规定数组可以从指向要复制到数组中的元素的指针初始化。

为什么会有这样的规则? “历史原因”。

【讨论】:

    【解决方案2】:

    我假设在前面的情况下"Hello World" 也降级为'H'0x4 的地址。

    并非如此:cString[] 在内存中获得了一个全新的地址。编译器为其分配了12个chars,并用"Hello World"字符串字面量的内容对其进行初始化。

    我假设"Hello World" 的计算结果是它的基地址0x4。在代码中使用字符串文字是否会将基地址返回到字符存储在内存中的“数组”?

    cString 稍后可能转换char*,产生它的基地址,但它在常规上下文中仍然是一个数组。特别是,如果您调用 sizeof(cString),您将获得数组的大小,而不是指针的大小。

    为什么不能使用 char 指针作为临时占位符来存储字符串字面量的基地址?

    你可以。但是,一旦将字符串文字分配给 char *,它就不再是字符串文字,至少就编译器而言。它变成了一个char * 指针,与其他char * 指针没有区别。

    请注意,现代 C 编译器将相同的字符串文字组合为优化,所以如果您编写

    #define HELLO_WORLD "Hello World"
    ...
    char* cptr = HELLO_WORLD;
    char cString[] = HELLO_WORLD;
    

    并打开优化,编译器将消除字符串文字的重复副本。

    【讨论】:

      【解决方案3】:

      第二个定义char cString[] = "Hello World"; 是这个等价定义的简写:

      char cString[12] = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0' };
      

      如果此定义作为全局范围出现或使用static 存储,则cString 将位于具有可执行映像中初始内容的.data 段中。如果它发生在具有自动存储功能的函数范围内,编译器将为数组分配自动存储空间(在堆栈框架上保留空间或等效空间)并生成代码以在运行时执行初始化。

      【讨论】:

      • 在抽象机中,有一个字符串字面量被复制到cString。就可观察行为而言,您给出的两个代码是相同的。实现它的选项可以描述为一个优化问题。
      • @M.M: true 但是 copying 字符串字面量有些模棱两可,它没有准确描述另一个例子的语义 char cString[12] = { "Hello" }; 等价于char cString[12] = { 'H', 'e', 'l', 'l', 'o', '\0', '\0', '\0', '\0', '\0', '\0', '\0' };,而不是@ 987654329@
      猜你喜欢
      • 2016-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-09
      • 1970-01-01
      • 2021-07-02
      • 2021-04-16
      • 2015-08-12
      相关资源
      最近更新 更多