在C语言中,可以在这样的声明中使用字符串文字:
char s[] = "hello";
或像这样:
char *s = "hello";
那么区别是什么呢? 我想知道在编译和运行时在存储持续时间方面实际发生了什么。
#1楼
只需添加:它们的大小也会得到不同的值。
printf("sizeof s[] = %zu\n", sizeof(s)); //6
printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8
如上所述,对于数组'\\0'
将分配为最后一个元素。
#2楼
区别在于
char *s = "Hello world";
将放置"Hello world"
在内存的只读部分 ,使s
的指针,使得在这个内存非法任何写入操作。
这样做时:
char s[] = "Hello world";
将文字字符串放入只读内存中,并将该字符串复制到堆栈上新分配的内存中。 因此
s[0] = 'J';
法律。
#3楼
char s[] = "hello";
声明s
为char
数组,该数组足以容纳初始化程序(5 + 1 char
s),并通过将给定字符串文字的成员复制到数组中来初始化该数组。
char *s = "hello";
声明s
到是指向一个或多个(更在这种情况下) char
S和点直接在包含文字固定(只读)位置"hello"
。
#4楼
此声明:
char s[] = "hello";
创建一个对象-大小为6的char
数组,称为s
,使用值'h', 'e', 'l', 'l', 'o', '\\0'
初始化。 该数组在内存中的分配位置以及生存期取决于声明出现的位置。 如果声明在函数内,则声明将一直存在,直到声明所在的块的末尾为止,并且几乎可以肯定地将其分配在堆栈上。 如果它在函数外部,则可能会存储在“初始化数据段”中,该段在程序运行时从可执行文件加载到可写内存中。
另一方面,此声明:
char *s ="hello";
创建两个对象:
- 包含值
'h', 'e', 'l', 'l', 'o', '\\0'
的6个char
的只读数组,该数组没有名称,并且具有静态存储持续时间 (这意味着在程序的整个生命周期中都活着); 和 - 一个指针类型为
s
的变量,称为s
,它使用该未命名只读数组中第一个字符的位置进行初始化。
未命名的只读数组通常位于程序的“文本”段中,这意味着它与代码本身一起从磁盘加载到只读存储器中。 s
指针变量在内存中的位置取决于声明出现的位置(就像第一个示例一样)。
#5楼
首先,在函数参数中,它们是完全等效的:
void foo(char *x);
void foo(char x[]); // exactly the same in all respects
在其他情况下, char *
分配一个指针,而char []
分配一个数组。 您问在前一种情况下字符串会去哪里? 编译器秘密分配一个静态匿名数组来保存字符串文字。 所以:
char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;
请注意,您绝不能尝试通过此指针修改此匿名数组的内容; 效果是不确定的(通常意味着崩溃):
x[1] = 'O'; // BAD. DON'T DO THIS.
使用数组语法直接将其分配到新的内存中。 因此修改是安全的:
char x[] = "Foo";
x[1] = 'O'; // No problem.
但是,该数组的生存时间仅与它的包含范围一样长,因此,如果在函数中执行此操作,请不要返回或泄漏指向该数组的指针-而是使用strdup()
或类似方法进行复制。 如果数组是在全局范围内分配的,那当然没问题。
#6楼
char s[] = "Hello world";
在这里, s
是一个字符数组,如果我们愿意,可以将其覆盖。
char *s = "hello";
字符串文字用于在指针s
指向的内存中的某个位置创建这些字符块。 我们可以通过更改它来重新分配它所指向的对象,但是只要它指向字符串文字,就不能更改它所指向的字符块。
#7楼
如果是:
char *x = "fred";
x是一个左值 -可以分配给它。 但在以下情况下:
char x[] = "fred";
x不是左值,而是右值-您无法分配给它。
#8楼
给出声明
char *s0 = "hello world";
char s1[] = "hello world";
假设以下假设内存映射:
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
数组(在C ++中为const char
),这意味着它的内存在程序启动时分配,并保持分配状态,直到程序终止。 尝试修改字符串文字的内容会调用未定义的行为。
线
char *s0 = "hello world";
将s0
定义为指向具有自动存储持续时间的char
的指针(这意味着变量s0
仅在声明它的作用域中存在)并向其复制字符串文字的地址 (在此示例中为0x00008000
)。 请注意,由于s0
指向字符串文字,因此不应将其用作任何试图对其进行修改的函数的参数(例如strtok()
, strcat()
, strcpy()
等)。
线
char s1[] = "hello world";
将s1
定义为具有12个元素的char
数组(长度从字符串文字中获取),并具有自动存储持续时间,并将文字内容复制到该数组中。 从内存映射中可以看到,我们有两个字符串"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
您可以重新分配变量s0
以指向其他字符串文字或另一个变量。 您不能将变量s1
重新分配为指向其他数组。
#9楼
根据这里的评论,应该显而易见的是:char * s =“ hello”; 这是一个坏主意,应在非常狭窄的范围内使用。
这可能是一个很好的机会,指出“常量正确性”是“好事”。 无论何时何地,都可以使用“ const”关键字来保护您的代码,以防“松弛”的调用者或程序员,这通常在指针起作用时最“松弛”。
足够的情节剧,这是在用“ const”修饰指针时可以实现的目标。 (注意:必须从右到左读取指针声明。)以下是在使用指针时保护自己的3种不同方式:
const DBJ* p means "p points to a DBJ that is const"
—也就是说,不能通过p更改DBJ对象。
DBJ* const p means "p is a const pointer to a DBJ"
—也就是说,可以通过p更改DBJ对象,但是不能更改指针p本身。
const DBJ* const p means "p is a const pointer to a const DBJ"
—也就是说,您不能更改指针p本身,也不能通过p更改DBJ对象。
与未遂常数突变有关的错误是在编译时捕获的。 const没有运行时空间或速度损失。
(当然,假设您正在使用C ++编译器?)
--DBJ
#10楼
C99 N1256草案
字符串文字有两种不同的用法:
初始化
char[]
:char c[] = "abc";
这是“更多的魔术”,在6.7.8 / 14“初始化”中进行了描述:
字符类型的数组可以由字符串文字初始化,并可选地用大括号括起来。 字符串文字的连续字符(如果有空间或数组大小未知,则包括终止空字符)将初始化数组的元素。
因此,这只是以下方面的捷径:
char c[] = {'a', 'b', 'c', '\\0'};
像任何其他常规数组一样,可以修改
c
。其他任何地方:都会产生:
- 未命名
- char数组C和C ++中的字符串文字类型是什么?
- 带有静态存储
- 如果修改后给出UB
所以当你写:
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";
定义类型为“ char的指针”的
p
并将其初始化为指向长度为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
#11楼
另外,请考虑一下,出于只读目的,两者的使用是相同的,您可以通过使用[]
或*(<var> + <index>)
格式建立索引来访问char:
printf("%c", x[1]); //Prints r
和:
printf("%c", *(x + 1)); //Prints r
显然,如果您尝试这样做
*(x + 1) = 'a';
当您尝试访问只读内存时,您可能会遇到分段错误。
#12楼
char *str = "Hello";
上面的设置str指向文本值“ Hello”,该值在程序的二进制映像中进行了硬编码,并在内存中标记为只读,这意味着对该String文本进行的任何更改都是非法的,并且会引发分段错误。
char str[] = "Hello";
将字符串复制到堆栈上新分配的内存中。 因此,允许对其进行任何更改都是合法的。
means str[0] = 'M';
将str更改为“ Mello”。
有关更多详细信息,请经历类似的问题:
为什么在写入以“ char * s”而不是“ char s []”初始化的字符串时出现分段错误?
#13楼
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify
// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal
来源:CSDN
作者:asdfgh0077
链接:https://blog.csdn.net/asdfgh0077/article/details/104013984