char s 和char * s有什么区别?
在C中,可以这样做:
char s[] = "hello";
要么
char *s = "hello";
所以我想知道有什么区别? 我想知道在编译时间和运行时间内存分配究竟发生了什么。
这里的区别是
char *s = "Hello world";
会把"Hello world"
放在内存的只读部分 ,并且指向这个指针,使得对这个内存的任何写操作都是非法的。
在做:
char s[] = "Hello world";
将string放入只读存储器,并将string复制到堆栈上新分配的内存中。 这样做
s[0] = 'J';
法律。
首先,在函数参数中,它们是完全等价的:
void foo(char *x); void foo(char x[]); // exactly the same in all respects (note! this only applies if the brackets are empty)
在其他上下文中, char *
分配一个指针,而char []
分配一个数组。 在前一种情况下,绳子在哪里?你问? 编译器偷偷分配一个静态匿名数组来保存string文字。 所以:
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()
或者类似的方法来创build一个副本。 如果数组在全局范围内分配,当然没有问题。
这个声明:
char s[] = "hello";
创build一个对象 – 一个大小为6的char
数组,名为s
,用值'h', 'e', 'l', 'l', 'o', '\0'
初始化。 这个数组在内存中的分配情况,以及它的存活时间取决于声明的出现位置。 如果声明在一个函数中,它将一直存在,直到它声明的块的结尾,并且几乎肯定会被分配到堆栈上。 如果它在一个函数之外,它可能会存储在一个“初始化数据段”中,当程序运行时,这个“初始化数据段”从可执行文件加载到可写入的内存中。
另一方面,这个声明:
char *s ="hello";
创build两个对象:
- 一个包含值为
'h', 'e', 'l', 'l', 'o', '\0'
的只读数组,它没有名字并且具有静态存储持续时间在整个生活中都活着); 和 - 一个名为
s
指向char的typesvariables,它被初始化为该未命名的只读数组中的第一个字符的位置。
未命名的只读数组通常位于程序的“文本”段中,这意味着它与代码一起从磁盘加载到只读存储器中。 s
指针variables在内存中的位置取决于声明的出现位置(就像第一个示例中一样)。
鉴于声明
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
string文字"hello world"
是char
(C ++中的const char
)的12个元素的数组,具有静态存储持续时间,这意味着程序启动时分配的内存将一直分配,直到程序终止。 尝试修改string文字的内容会调用未定义的行为。
该线
char *s0 = "hello world";
将s0
定义为指向具有自动存储持续时间的char
的指针(意思是variabless0
只存在于声明的范围内),并将string文本(本例中为0x00008000
)的地址复制到它。 请注意,因为s0
指向一个string字面值,所以它不应该被用作任何试图修改它的函数的参数(例如, strtok()
, strcat()
, strcpy()
等等)。
该线
char s1[] = "hello world";
将s1
定义为char
元素的12元素数组(长度取自string文字),并自动存储持续时间,并将文本的内容复制到数组中。 从内存映射中可以看到,我们有两个string"hello world"
; 不同之处在于您可以修改s1
包含的string。
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
您可以重新分配variabless0
以指向不同的string文字或另一个variables。 您不能重新指定variabless1
指向不同的数组。
C99 N1256草案
数组文字有两种完全不同的用法:
-
初始化
char[]
:char c[] = "abc";
这更“神奇”,并在6.7.8 / 14“初始化”中描述 :
字符types的数组可以由string文字初始化,可选地用大括号括起来。 string文字的连续字符(包括终止空字符,如果有空间或数组未知大小)初始化数组的元素。
所以这只是一个捷径:
char c[] = {'a', 'b', 'c', '\0'};
像任何其他常规数组一样,
c
可以被修改。 -
其他地方:它会产生:
- 无名
- char数组什么是C和C ++中的string文字的types?
- 与静态存储
- 如果修改则给予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“string文字”中有logging :
5在翻译阶段7,将string或文字产生的每个多字节字符序列附加一个字节或值为零的代码。 然后使用多字节字符序列来初始化静态存储持续时间和长度的数组,以便足以包含该序列。 对于string文字,数组元素的types为char,并且用多字节字符序列的单个字节进行初始化[…]
6这些数组是否是不同的,只要它们的元素具有适当的值。 如果程序试图修改这样一个数组,行为是不确定的。
6.7.8 / 32“初始化”给出了一个直接的例子:
例8:声明
char s[] = "abc", t[3] = "abc";
定义“简单的”字符数组对象
s
和t
其元素用string文字初始化。这个声明是一样的
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
数组的内容是可修改的。 另一方面,声明
char *p = "abc";
定义
p
的types为“指向char的指针”并将其初始化为指向长度为4的types为“char的数组”的对象,其元素用string文字初始化。 如果尝试使用p
来修改数组的内容,则行为是不确定的。
GCC 4.8 x86-64 ELF实现
程序:
#include <stdio.h> int main() { 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在.rodata
节中存储char*
, 而不是在.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
char s[] = "hello";
声明s
是一个足够长的持有初始化方法的char
数组(5 + 1个char
),并通过将给定string的成员复制到数组中来初始化数组。
char *s = "hello";
声明s
是指向一个或多个(本例中为更多) char
s的指针,并将其直接指向包含文字"hello"
的固定(只读)位置。
作为补充,考虑到对于只读目的,两者的使用是相同的,您可以通过索引[]
或*(<var> + <index>)
格式来访问char:
printf("%c", x[1]); //Prints r
和:
printf("%c", *(x + 1)); //Prints r
显然,如果你试图做
*(x + 1) = 'a';
当您尝试访问只读内存时,您可能会遇到分段错误。
只是补充一点:你也可以得到不同的尺寸值。
printf("sizeof s[] = %zu\n", sizeof(s)); //6 printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8
如上所述,对于数组'\0'
将被分配为最终元素。
char s[] = "Hello world";
在这里, s
是一个字符数组,如果我们愿意的话可以被覆盖。
char *s = "hello";
string文字用于在指针所指向的内存中的某处创build这些字符块。 我们可以通过改变它来重新指定它指向的对象,但是只要它指向一个string文字,它所指向的字符块就不能被改变。
char *str = "Hello";
上面设置str指向在程序的二进制图像中硬编码的字面值“Hello”,在内存中标记为只读,意味着此Stringstring中的任何改变都是非法的,并且会引起分段错误。
char str[] = "Hello";
将string复制到堆栈上新分配的内存。 因此,任何改变是允许的和合法的。
means str[0] = 'M';
将改变“梅洛”。
有关更多详细信息,请通过类似的问题:
为什么在写入用“char * s”初始化而不是“char s []”的string时会出现分段错误?
如果是:
char *x = "fred";
x是一个左值 – 它可以被分配给。 但在以下情况下:
char x[] = "fred";
x不是一个左值,它是一个右值 – 你不能指定它。
根据这里的评论应该是显而易见的:char * s =“hello”; 这是一个坏主意,应该在狭窄的范围内使用。
这可能是一个很好的机会,指出“常量正确”是一件“好事”。 无论何时何地,都可以使用“const”关键字来保护你的代码,从“放松的”调用者或程序员那里,当指针进场时通常是最“放松”的。
足够的情节剧,这是用“const”装饰指针时可以达到的效果。 (注意:必须从右向左阅读指针声明。)在使用指针时,有三种不同的方式来保护自己:
const DBJ* p means "p points to a DBJ that is const"
– 也就是说,DBJ对象不能通过p来改变。
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-ant突变相关的错误。 const没有运行时间空间或速度惩罚。
(假设你正在使用C ++编译器,当然?)
–DBJ