C 数组初始化 - 一些基础知识

C array initializing - some basics

我在一本书上看到,当你有一个像这样的字符串"blabla",这意味着有一个隐藏的字符数组,而这个表达式returns地址到第一个元素 ,它就像一个 const 数组。

这让我对两种情况感到困惑:

  1. char a[7] = "blabla" ,是不可能的,因为 "blabla" returns 一个 address 到数组的第一个元素,那么如何将地址放入 a 而不是实际元素?

  2. 它说当你看到 "blabla" 时,它表示像 const 字符数组,这意味着我根本无法更改 a(这不是真的)。​​

我想我不清楚这里真正基本的东西。

第一种情况,

char a[7] = "blabla" , is not possible [...]

是的,有可能,这是一个初始化

引用 C11,章节 §6.7.9/P14,初始化

An array of character type may be initialized by a character string literal or UTF−8 string literal, optionally enclosed in braces. Successive bytes of the string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array.

第二种情况,

it says when you see "blabla" it means like a const char array , and that means I can't change a, at all (which is not true).

[从直接尝试修改字符串文字的角度来看]

可以,但必须不可以。

来自章节 §6.4.5

[...] If the program attempts to modify such an array, the behavior is undefined.

也就是说,在你的情况下a不是指向字符串文字的指针,它是一个数组, 元素用字符串文字的内容初始化。您完全可以修改 a 数组的内容。

"blabla"是书上说的,一个7字节的字符数组,最后一个是'\0',放在一个只读数据space(可能的话)。

(1) 当你写:

 char a[7] = "blabla";

您告诉编译器在堆栈上创建一个包含 7 个字符的可变数组,并将只读数组复制到其中。请注意,您还可以写:

 char a[] = "blabla";

...这样更安全,因为编译器会为您计算字符数。

(2) 鉴于 a[] 是 "blabla" 的副本,您可以毫无问题地写入它。如果你想保持只读 属性 你可以写:

const char *a = "blabla";

这次 a 将是指向常量字符串的 const 指针,其 内容 将不可变。无论如何您都可以重新分配指针:

const char *a = "blabla";
a = "blublu";

根据 C 标准(6.3.2.1 左值、数组和函数指示符)

3 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

所以在这个声明中

char a[7] = "blabla";

由于包含终止零作为字符串文字的元素而具有字符数组char[7]类型的字符串文字的元素用于初始化字符数组的元素a

实际上这个声明等同于声明

char a[7] = { 'b', 'l', 'a', 'b', 'l', 'a', '[=11=]' };

考虑到在 C 中,字符串文字具有非常量字符数组的类型。然而,它们本身可能无法修改。

来自 C 标准(6.4.5 字符串文字)

7 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

所以你可以这样写

char *s = "blabla";

在这种情况下,根据 C 标准的第一条引述,字符串文字被转换为指向其第一个元素的指针,指针的值被分配给变量 s

即在静态内存中创建了一个未命名的字符数组,并将数组第一个元素的地址赋值给指针s。您可能不会使用指针来更改您可能不会写的文字

char *s = "blabla";
s[0] = 'B';

在 C++ 中,字符串文字确实具有常量字符数组类型。所以你必须用C++程序来写

const char *s = "blabla";

在 C 中你也可以这样写

char a[6] = "blabla";
     ^^^^

在这种情况下,字符串文字的终止零将不会用于初始化字符数组 a 。所以数组将不包含字符串。

在 C++ 中这样的声明是无效的。

太晚了,但我仍然提供我的答案。

所以让我们来区分一下

main()
{
  char *a="blabla";
  a[3]='x';
}

还有这个,你的。

main() 
{
  char a[7] = "blabla"
  a[3]='x';
}

所以他们之间有很大的区别。

在第一种情况下,对象 a 是一个指针,其值指向 blabla 字符串的开头。

转储汇编代码,我们看到:

  4004aa:       48 c7 45 f8 54 05 40    movq   [=12=]x400554,-0x8(%rbp)
  4004b1:       00 
  4004b2:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  4004b6:       48 83 c0 03             add    [=12=]x3,%rax
  4004ba:       c6 00 78                movb   [=12=]x78,(%rax)

因此,它尝试将指针设置为地址 0x400554

Objdumpo 报告此地址在 .rodata 段中。

.rodata 部分的反汇编:

0000000000400550 <_IO_stdin_used>:
  400550:       01 00                   add    %eax,(%rax)
  400552:       02 00                   add    (%rax),%al
  400554:       62                      (bad)  
  400555:       6c                      insb   (%dx),%es:(%rdi)
  400556:       61                      (bad)  
  400557:       62                      .byte 0x62
  400558:       6c                      insb   (%dx),%es:(%rdi)
  400559:       61                      (bad)  

因此,编译器在该地址的 .rodata 中安装了字符串 blabla,然后它尝试修改 .rodata 段,以段错误结束。

readelf 报告没有 W 访问 .rodata:

[13] .rodata           PROGBITS         0000000000400550  00000550
     000000000000000b  0000000000000000   A       0     0     4

另一方面,您尝试做的(第二个程序)是这样编译的:

00000000004004a6 <main>:
  4004a6:       55                      push   %rbp
  4004a7:       48 89 e5                mov    %rsp,%rbp
  4004aa:       c7 45 f0 62 6c 61 62    movl   [=15=]x62616c62,-0x10(%rbp)
  4004b1:       66 c7 45 f4 6c 61       movw   [=15=]x616c,-0xc(%rbp)
  4004b7:       c6 45 f6 00             movb   [=15=]x0,-0xa(%rbp)
  4004bb:       c6 45 f3 78             movb   [=15=]x78,-0xd(%rbp)

在这种情况下,数组对象 a 在堆栈帧上分配了 7 个字节,从偏移量 %RBP-0xA 开始到 %RBP-0x10

当它尝试执行 a[3]='x' 时,它将修改 %RBP-0xD 处的堆栈。堆栈有write权限,一切正常

有关更多信息,我建议您阅读 https://en.wikipedia.org/wiki/Identity_and_change