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

时间:2017-05-25 08:05:56

标签: c arrays string initialization string-literals

我在一本书中读到,当你有一个像"blabla"这样的字符串时,它意味着有一个隐藏的char数组,而这个表达式将地址返回给第一个元素 ,它就像一个 const 数组。

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

  1. char a[7] = "blabla"是不可能的,因为“blabla”会将 地址 返回到数组的第一个元素,所以你会怎么做地址为a而非实际元素?

  2. 它说当你看到“blabla”时它意味着像const char数组,这意味着我根本不能改变a(这不是真的)。 / p>

  3. 我想这里真正基本的东西对我来说还不清楚。

4 个答案:

答案 0 :(得分:4)

根据C标准(6.3.2.1 Lvalues,数组和函数指示符)

  

3 除非 sizeof运算符的操作数或   一元&运算符,或者是用于初始化的字符串文字   数组,将类型为''array of type''的表达式转换为   带有''指向类型'的指针的表达式,指向初始值   数组对象的元素,而不是左值。如果是数组对象   具有寄存器存储类,行为未定义。

所以在这个宣言中

char a[7] = "blabla";

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

事实上,这个声明等同于声明

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

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

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

  

7未指明这些阵列是否与它们不同   元素具有适当的值。如果程序试图   修改这样的数组,行为是未定义的。

所以你可以写一些例如

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 ++中,这样的声明无效。

答案 1 :(得分:3)

第一种情况,

  

char a[7] = "blabla",是不可能的[...]

是的,这是可能的,这是初始化

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

  

字符数组的数组可以用字符串文字或UTF−8字符串初始化   文字,可选择括在括号中。字符串文字的连续字节(包括   如果有空间或数组的大小未知,则终止空字符)初始化   数组的元素。

第二种情况,

  

它说当你看到“blabla”时它就像一个const char数组,这意味着我根本无法改变a(这不是真的)。​​

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

你可以,但你必须没有。

从章节§6.4.5

  

[...]如果程序试图修改这样的数组,行为是   未定义。

也就是说,在 你的情况 中,a不是指向字符串文字的指针,它是一个数组,其元素初始化为来自字符串文字。完全允许您修改a数组的内容。

答案 2 :(得分:2)

&#34;布拉布拉&#34;这是本书所说的,一个7字节的字符数组,最后一个是&#39; \ 0&#39;,放在只读数据空间中(如果可能的话)。

(1)当你写:

 char a[7] = "blabla";

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

 char a[] = "blabla";

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

(2)鉴于[]是&#34; blabla&#34;的副本。你可以毫无问题地写信给它。如果你想保留只读属性,你可以写:

const char *a = "blabla";

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

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

答案 3 :(得分:1)

为时已晚,但我仍然提供了答案。

所以让我们区分

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   $0x400554,-0x8(%rbp)
  4004b1:       00 
  4004b2:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  4004b6:       48 83 c0 03             add    $0x3,%rax
  4004ba:       c6 00 78                movb   $0x78,(%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报告.rodata上没有W访问权限:

[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   $0x62616c62,-0x10(%rbp)
  4004b1:       66 c7 45 f4 6c 61       movw   $0x616c,-0xc(%rbp)
  4004b7:       c6 45 f6 00             movb   $0x0,-0xa(%rbp)
  4004bb:       c6 45 f3 78             movb   $0x78,-0xd(%rbp)

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

当它试图做[3] =&#39; x&#39;它将修改%RBP-0xD处的堆栈。堆栈有write权限,一切都可以。

有关详情,建议您阅读https://en.wikipedia.org/wiki/Identity_and_change