在C中,可以在声明中使用字符串文字,如下所示:
char s[] = "hello";
Run Code Online (Sandbox Code Playgroud)
或者像这样:
char *s = "hello";
Run Code Online (Sandbox Code Playgroud)
那么区别是什么呢?我想知道在编译和运行时的存储持续时间实际发生了什么.
Ric*_*ard 533
这里的区别在于
char *s = "Hello world";
Run Code Online (Sandbox Code Playgroud)
将放置"Hello world"在内存的只读部分,并s指向该指针使得此内存上的任何写入操作都是非法的.
做的时候:
char s[] = "Hello world";
Run Code Online (Sandbox Code Playgroud)
将文字字符串放在只读内存中,并将字符串复制到堆栈上新分配的内存中.从而制作
s[0] = 'J';
Run Code Online (Sandbox Code Playgroud)
法律.
bdo*_*lan 148
首先,在函数参数中,它们完全等效:
void foo(char *x);
void foo(char x[]); // exactly the same in all respects
Run Code Online (Sandbox Code Playgroud)
在其他上下文中,char *分配指针,同时char []分配一个数组.你问,在前一种情况下字符串在哪里?编译器秘密分配一个静态匿名数组来保存字符串文字.所以:
char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;
Run Code Online (Sandbox Code Playgroud)
请注意,您不能尝试通过此指针修改此匿名数组的内容; 效果未定义(通常意味着崩溃):
x[1] = 'O'; // BAD. DON'T DO THIS.
Run Code Online (Sandbox Code Playgroud)
使用数组语法直接将其分配到新内存中.因此修改是安全的:
char x[] = "Foo";
x[1] = 'O'; // No problem.
Run Code Online (Sandbox Code Playgroud)
但是,阵列只能在其范围内存在,因此如果您在函数中执行此操作,请不要返回或泄漏指向此数组的指针 - 使用strdup()或类似地制作副本.如果数组在全局范围内分配,当然没问题.
caf*_*caf 70
本声明:
char s[] = "hello";
Run Code Online (Sandbox Code Playgroud)
创建一个对象 - 一个char大小为6 的数组,称为s,使用值初始化'h', 'e', 'l', 'l', 'o', '\0'.这个数组在内存中分配的位置,以及它的存在时间取决于声明出现的位置.如果声明在一个函数内,它将一直存在到声明它的块的结尾,并且几乎肯定会在栈上分配; 如果它在函数之外,它可能存储在"初始化数据段"中,该"初始化数据段"在程序运行时从可执行文件加载到可写存储器中.
另一方面,这个声明:
char *s ="hello";
Run Code Online (Sandbox Code Playgroud)
创建两个对象:
char包含值'h', 'e', 'l', 'l', 'o', '\0',没有名称且具有静态存储持续时间(意味着它在程序的整个生命周期中存在); 和s,该变量使用该未命名的只读数组中第一个字符的位置进行初始化.未命名的只读数组通常位于程序的"文本"段中,这意味着它从磁盘加载到只读存储器中,以及代码本身.s指针变量在内存中的位置取决于声明出现的位置(就像在第一个示例中一样).
Joh*_*ode 59
鉴于声明
char *s0 = "hello world";
char s1[] = "hello world";
Run Code Online (Sandbox Code Playgroud)
假设以下假设记忆图:
0x01 0x02 0x03 0x04
0x00008000: 'h' 'e' 'l' 'l'
0x00008004: 'o' ' ' 'w' 'o'
0x00008008: 'r' 'l' 'd' 0x00
...
s0: 0x00010000: 0x00 0x00 0x80 0x00
s1: 0x00010004: 'h' 'e' 'l' 'l'
0x00010008: 'o' ' ' 'w' 'o'
0x0001000C: 'r' 'l' 'd' 0x00
字符串文字"hello world"是一个12元素的数组char(const char在C++中),具有静态存储持续时间,这意味着它的内存在程序启动时分配,并保持分配直到程序终止.尝试修改字符串文字的内容会调用未定义的行为.
这条线
char *s0 = "hello world";
Run Code Online (Sandbox Code Playgroud)
定义s0为char具有自动存储持续时间的指针(意味着该变量s0仅存在于声明它的范围内)并将字符串文字的地址(0x00008000在此示例中)复制到它.请注意,由于s0指向一个字符串字面量,它不应该被用来作为一个参数,将试图修改它的任何功能(如strtok(),strcat(),strcpy(),等).
这条线
char s1[] = "hello world";
Run Code Online (Sandbox Code Playgroud)
定义s1为char具有自动存储持续时间的12元素数组(长度取自字符串文字),并将文字的内容复制到数组.从内存映射中可以看出,我们有两个字符串副本"hello world"; 区别在于您可以修改其中包含的字符串s1.
s0并且s1在大多数情况下可以互换; 以下是例外情况:
sizeof s0 == sizeof (char*)
sizeof s1 == 12
type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char
Run Code Online (Sandbox Code Playgroud)
您可以重新分配变量s0以指向不同的字符串文字或另一个变量.您无法将变量重新指定s1为指向其他数组.
Cir*_*四事件 30
C99 N1256草案
字符串文字有两种不同的用法:
初始化char[]:
char c[] = "abc";
Run Code Online (Sandbox Code Playgroud)
这是"更神奇",并在6.7.8/14"初始化"中描述:
字符类型数组可以由字符串文字初始化,可选地用大括号括起来.字符串文字的连续字符(如果有空间或数组大小未知,则包括终止空字符)初始化数组的元素.
所以这只是一个捷径:
char c[] = {'a', 'b', 'c', '\0'};
Run Code Online (Sandbox Code Playgroud)
像任何其他常规数组一样,c可以进行修改.
其他地方:它产生一个:
所以当你写:
char *c = "abc";
Run Code Online (Sandbox Code Playgroud)
这类似于:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
Run Code Online (Sandbox Code Playgroud)
注意来自char[]to 的隐式转换char *,这总是合法的.
然后,如果你修改c[0],你也修改__unnamed,这是UB.
这在6.4.5"字符串文字"中记录:
5在转换阶段7中,将值0的字节或代码附加到由字符串文字或文字产生的每个多字节字符序列.然后使用多字节字符序列初始化静态存储持续时间和长度的数组,该数组足以包含序列.对于字符串文字,数组元素的类型为char,并使用多字节字符序列的各个字节进行初始化[...]
6如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的.如果程序试图修改此类数组,则行为未定义.
6.7.8/32"初始化"给出了一个直接的例子:
例8:声明
Run Code Online (Sandbox Code Playgroud)char s[] = "abc", t[3] = "abc";定义"plain"char数组对象
s,t其元素使用字符串文字初始化.该声明与之相同
Run Code Online (Sandbox Code Playgroud)char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };数组的内容是可修改的.另一方面,声明
Run Code Online (Sandbox Code Playgroud)char *p = "abc";
p使用"指向char的指针"类型定义并将其初始化为指向类型为"array of char"且长度为4的对象,其元素使用字符串文字初始化.如果尝试使用p修改数组的内容,则行为未定义.
GCC 4.8 x86-64 ELF实施
程序:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译和反编译:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Run Code Online (Sandbox Code Playgroud)
输出包含:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Run Code Online (Sandbox Code Playgroud)
结论:GCC将char*其存储在.rodata部分中,而不是存储在.text.
如果我们这样做char[]:
char s[] = "abc";
Run Code Online (Sandbox Code Playgroud)
我们获得:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
Run Code Online (Sandbox Code Playgroud)
所以它存储在堆栈中(相对于%rbp).
但是请注意,默认的链接脚本提出.rodata并.text在同一网段,它具有执行,但没有写权限.这可以通过以下方式观察:
readelf -l a.out
Run Code Online (Sandbox Code Playgroud)
其中包含:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
Run Code Online (Sandbox Code Playgroud)
CB *_*ley 15
char s[] = "hello";
Run Code Online (Sandbox Code Playgroud)
声明s是一个数组,char其长度足以保存初始化器(5 + 1 char)并通过将给定字符串文字的成员复制到数组中来初始化数组.
char *s = "hello";
Run Code Online (Sandbox Code Playgroud)
声明s是指向一个或多个(在本例中为更多)char的指针,并将其直接指向包含文字的固定(只读)位置"hello".