Answers:
请参阅C FAQ,问题1.32
问:这些初始化之间有什么区别?
char a[] = "string literal";
char *p = "string literal";
如果我尝试为分配新值,程序将崩溃p[i]
。答:字符串文字(C源代码中双引号字符串的正式术语)可以以两种略有不同的方式使用:
- 作为char数组的初始化程序,如在的声明中
char a[]
,它指定该数组中字符的初始值(并在必要时指定其大小)。- 在其他任何地方,它都变成一个未命名的静态字符数组,并且该未命名的数组可以存储在只读存储器中,因此不必修改。在表达式上下文中,通常将数组立即转换为指针(请参见第6节),因此第二个声明将p初始化为指向未命名数组的第一个元素。
一些编译器具有控制字符串文字是否可写的开关(用于编译旧代码),而某些编译器可能具有将字符串文字形式上视为const char数组的选项(以更好地捕获错误)。
mprotect
波动只读保护来实现(请参阅此处)。
通常,程序运行时,字符串文字会存储在只读存储器中。这是为了防止您意外更改字符串常量。在第一个示例中,"string"
将其存储在只读存储器中并*str
指向第一个字符。当您尝试将第一个字符更改为时,会发生段错误'z'
。
在第二个例子中,字符串"string"
被复制从其只读家里的编译器str[]
阵列。然后允许更改第一个字符。您可以通过打印每个地址来进行检查:
printf("%p", str);
同样,str
在第二个示例中打印的大小将显示编译器已为其分配7个字节:
printf("%d", sizeof(str));
这些答案大多数都是正确的,但仅仅是为了增加一些清晰度。
人们所指的“只读内存”是ASM术语中的文本段。指令在内存中位于同一位置。出于安全性等明显原因,这是只读的。当您创建一个初始化为字符串的char *时,字符串数据将被编译到文本段中,并且程序会初始化指针以指向该文本段。因此,如果您尝试更改它,请kaboom。Segfault。
当以数组形式编写时,编译器会将初始化的字符串数据放置在数据段中,这与您的全局变量和此类变量所在的位置相同。该存储器是可变的,因为数据段中没有指令。这次,当编译器初始化字符数组(仍然只是一个char *)时,它指向的是数据段而不是文本段,您可以在运行时安全地对其进行更改。
为什么在写入字符串时出现分段错误?
C99 N1256草案
字符串文字有两种不同的用法:
初始化char[]
:
char c[] = "abc";
这是“更多的魔术”,在6.7.8 / 14“初始化”中进行了描述:
字符类型数组可以由字符串文字初始化,并可选地用大括号括起来。字符串文字的连续字符(如果有空间或数组大小未知,则包括终止空字符)将初始化数组的元素。
因此,这只是以下方面的捷径:
char c[] = {'a', 'b', 'c', '\0'};
像任何其他常规数组一样,c
可以进行修改。
其他任何地方:都会产生:
所以当你写:
char *c = "abc";
这类似于:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
请注意从隐式强制char[]
转换为char *
始终是合法的。
然后,如果您进行修改c[0]
,那么您也将进行修改__unnamed
UB。
在6.4.5“字符串文字”中有记录:
5在转换阶段7中,将一个或多个字符串文字产生的每个多字节字符序列附加一个零值的字节或代码。然后,多字节字符序列用于初始化一个足以包含该序列的静态存储持续时间和长度数组。对于字符串文字,数组元素的类型为char,并使用多字节字符序列的各个字节进行初始化[...]
6如果这些数组的元素具有适当的值,则不确定这些数组是否不同。如果程序尝试修改这样的数组,则行为是不确定的。
6.7.8 / 32“初始化”给出了一个直接的示例:
例8:声明
char s[] = "abc", t[3] = "abc";
定义“普通”字符数组对象,
s
并t
其元素用字符串文字初始化。此声明与
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
数组的内容是可修改的。另一方面,声明
char *p = "abc";
定义
p
类型为“ char的指针”,并将其初始化为长度为4的“ char数组”类型的对象,该对象的元素使用字符串文字进行初始化。如果试图使用它p
来修改数组的内容,则该行为是不确定的。
GCC 4.8 x86-64 ELF实施
程序:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
编译和反编译:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
输出包含:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
结论:GCC将char*
其存储在.rodata
部分中,而不是在中.text
。
如果我们这样做char[]
:
char s[] = "abc";
我们获得:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
因此它被存储在堆栈中(相对于 %rbp
)。
但是请注意,默认链接程序脚本将.rodata
和.text
放在同一段中,该段具有执行但没有写许可权。这可以通过以下方式观察到:
readelf -l a.out
其中包含:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
在第一个代码中,“字符串”是一个字符串常量,永远不要修改字符串常量,因为它们经常被放置在只读存储器中。“ str”是用于修改常量的指针。
在第二个代码中,“ string”是一个数组初始值设定项,对于
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
“ str”是在堆栈上分配的数组,可以自由修改。
str
是global或)static
。
因为在"whatever"
第一个示例的上下文中的类型是const char *
(即使您将其分配给非const char *),这意味着您不应尝试对其进行写入。
编译器通过将字符串放入内存的只读部分来强制执行此操作,因此对其进行写入会生成段错误。
要了解此错误或问题,您应该首先了解b / w与指针和数组的区别,因此在这里我首先向您解释b / w与他们的区别
char strarray[] = "hello";
在内存数组中存储在连续存储单元中,按[h][e][l][l][o][\0] =>[]
1个字符字节大小的存储单元存储,并且此连续存储单元可以通过名为strarray的名称访问。strarray
本身包含初始化为它的所有字符串。在"hello"
这种情况下,我们可以通过按索引值访问每个字符来轻松更改其内存内容
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
并且其值更改为 'm'
strarray值更改为"mello"
;
需要注意的一点是,我们可以通过逐个字符地更改字符串数组的内容,但是不能像这样直接初始化其他字符串 strarray="new string"
无效为
众所周知,指针指向内存中的内存位置,未初始化的指针指向随机的内存位置,因此在初始化之后指向特定的内存位置
char *ptr = "hello";
此处指针ptr初始化为字符串"hello"
,该字符串是存储在只读存储器(ROM)中的常量字符串,因此"hello"
不能更改,因为它存储在ROM中
ptr存储在堆栈部分并指向常量字符串 "hello"
所以ptr [0] ='m'无效,因为您无法访问只读存储器
但是ptr可以直接初始化为其他字符串值,因为它只是指针,因此可以指向其数据类型的变量的任何内存地址
ptr="new string"; is valid
char *str = "string";
分配一个指向字符串文字的指针,编译器将其放置在可执行文件的不可修改部分中;
char str[] = "string";
分配并初始化可修改的本地数组
int *b = {1,2,3)
像写一样写char *s = "HelloWorld"
吗?
的C常见问题是@matli挂提到它,但没有一个人在这里还没有,所以澄清:如果一个字符串(在源双引号字符串)用于任何地方以外初始化字符数组(即:@ Mark的第二个示例正确运行),该字符串由编译器存储在特殊的静态字符串表中,该表类似于创建本质上是匿名的全局静态变量(当然是只读的)(没有变量“ name”) ”)。该只读部分是重要组成部分,也是为什么@马克的第一个代码示例段错误。
int *b = {1,2,3)
像写一样写char *s = "HelloWorld"
吗?
的
char *str = "string";
行定义了一个指针,并将其指向文字字符串。文字字符串不可写,因此在执行以下操作时:
str[0] = 'z';
您遇到段错误。在某些平台上,文字可能在可写内存中,因此您不会看到段错误,但是无论如何它都是无效的代码(导致未定义的行为)。
该行:
char str[] = "string";
分配一个字符数组并将文字字符串复制到该数组中,这是完全可写的,因此后续更新没有问题。
int *b = {1,2,3)
像写一样写char *s = "HelloWorld"
吗?
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";
// create an array of characters like this
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];
// now we try to change a character in the array first, this will work
*arr_p = 'E';
// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.
/*-----------------------------------------------------------------------------
* String constants can't be modified. A segmentation fault is the result,
* because most operating systems will not allow a write
* operation on read only memory.
*-----------------------------------------------------------------------------*/
//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array.
首先,str
是一个指向的指针"string"
。允许编译器将字符串文字放在不能写入但只能读取的内存中。(这确实应该触发警告,因为您正在将分配const char *
给char *
。是否禁用了警告,还是只是忽略了它们?)
其次,您要创建一个数组,该数组是您具有完全访问权限的内存,并使用对其进行初始化"string"
。您正在创建一个char[7]
(六个字母,一个用于结尾的“ \ 0”),然后您可以随便使用它。
const
前缀使变量为只读
char [N]
,不是const char [N]
,因此没有警告。(您至少可以通过传递来更改gcc中的值-Wwrite-strings
。)
当您尝试访问无法访问的内存时,导致分段错误。
char *str
是指向不可修改的字符串的指针(获取段错误的原因)。
而是char str[]
一个数组,可以修改。