提问人: 提问时间:11/10/2009 最后编辑:StoryTeller - Unslander Monica 更新时间:11/10/2021 访问量:393094
char s[] 和 char *s 有什么区别?
What is the difference between char s[] and char *s?
问:
在 C 中,可以在如下所示的声明中使用字符串文字:
char s[] = "hello";
或者像这样:
char *s = "hello";
那么有什么区别呢?我想知道在编译和运行时,在存储持续时间方面实际发生了什么。
答:
这里的区别在于
char *s = "Hello world";
将放置在内存的只读部分,并且指向该部分的指针会使该内存上的任何写入操作都是非法的。"Hello world"
s
在做的时候:
char s[] = "Hello world";
将文本字符串放在只读内存中,并将字符串复制到堆栈上新分配的内存中。从而使
s[0] = 'J';
法律。
评论
"Hello world"
char msg[] = "hello, world!";
char * const
char s[] = "hello";
声明为一个数组,其长度足以容纳初始值设定项 (5 + 1 秒),并通过将给定字符串文本的成员复制到数组中来初始化数组。s
char
char
char *s = "hello";
声明为指向一个或多个(在本例中为更多)的指针,并将其直接指向包含文本的固定(只读)位置。s
char
"hello"
评论
s
const char
本声明:
char s[] = "hello";
创建一个对象 - 一个大小为 6 的数组,称为 ,使用值 进行初始化。此数组在内存中的分配位置以及生存期取决于声明的出现位置。如果声明在函数中,它将一直存在到声明它的块的末尾,并且几乎可以肯定地被分配在堆栈上;如果它位于函数之外,它可能会存储在“初始化的数据段”中,该段在程序运行时从可执行文件加载到可写内存中。char
s
'h', 'e', 'l', 'l', 'o', '\0'
另一方面,此声明:
char *s ="hello";
创建两个对象:
- 一个 6 秒的只读数组,其中包含值,它没有名称,并且具有静态存储持续时间(这意味着它在程序的整个生命周期中都存在);和
char
'h', 'e', 'l', 'l', 'o', '\0'
- 指向 char 的指针类型的变量,称为 ,该变量使用该未命名的只读数组中第一个字符的位置进行初始化。
s
未命名的只读数组通常位于程序的“文本”段中,这意味着它与代码本身一起从磁盘加载到只读内存中。指针变量在内存中的位置取决于声明的出现位置(就像在第一个示例中一样)。s
评论
char s[] = "hello"
"hello"
s
"hello"
s
s
movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
.rodata
.text
char s[] = "Hello world";
copies the string to newly allocated memory on the stack
char s[] = "Hellow world";
s
s
首先,在函数参数中,它们是完全等价的:
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()
char s[] = "Hello world";
这里是一个字符数组,如果我们愿意,可以覆盖它们。s
char *s = "hello";
字符串文本用于在此指针指向的内存中的某个位置创建这些字符块。在这里,我们可以通过更改它来重新分配它所指向的对象,但只要它指向字符串文字,它所指向的字符块就不能更改。s
评论
在以下情况下:
char *x = "fred";
x 是一个左值 -- 可以赋值给它。但在以下情况下:
char x[] = "fred";
x 不是左值,而是右值 -- 你不能赋值给它。
评论
x
鉴于声明
char *s0 = "hello world";
char s1[] = "hello world";
假设以下假设的内存映射(列表示与给定行地址偏移量为 0 到 3 的字符,因此,例如右下角的 地址 = ):0x00
0x0001000C + 3
0x0001000F
+0 +1 +2 +3 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
字符串文字是一个包含 12 个元素的数组(在 C++ 中),具有静态存储持续时间,这意味着它的内存在程序启动时分配,并保持分配状态,直到程序终止。尝试修改字符串文本的内容会调用未定义的行为。"hello world"
char
const char
生产线
char *s0 = "hello world";
定义为指向自动存储持续时间的指针(意味着该变量仅存在于声明它的作用域中),并将字符串文本(在此示例中为)的地址复制到该变量。请注意,由于指向字符串文字,因此不应将其用作任何尝试修改它的函数的参数(例如,、等)。s0
char
s0
0x00008000
s0
strtok()
strcat()
strcpy()
生产线
char s1[] = "hello world";
定义为具有自动存储持续时间的 12 个元素数组(长度取自字符串文本),并将文本的内容复制到数组中。从内存映射中可以看出,我们有两个字符串副本;区别在于您可以修改 中包含的字符串。s1
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
评论
0x00 0x01 0x02 0x03
0x01 0x02 0x03 0x04
s0
根据这里的注释,应该很明显: char * s = “hello” ; 这是一个坏主意,应该在非常狭窄的范围内使用。
这可能是一个很好的机会,可以指出“常量正确性”是一件“好事”。无论何时何地,都可以使用“const”关键字来保护您的代码免受“宽松”调用者或程序员的侵害,当指针发挥作用时,这些调用者或程序员通常是最“宽松”的。
够了情节剧,这是用“const”装饰指针时可以实现的效果。 (注意:必须从右到左读取指针声明。 以下是玩指针时保护自己的 3 种不同方法:
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
评论
补充一点:您还可以获得不同大小的值。
printf("sizeof s[] = %zu\n", sizeof(s)); //6
printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8
如上所述,对于数组将被分配为最终元素。'\0'
C99 N1256 草案
字符串文本有两种不同的用法:
初始化:
char[]
char c[] = "abc";
这是“更神奇的”,并在 6.7.8/14 “初始化”中进行了描述:
字符类型的数组可以由字符串文本初始化(可选) 用大括号封闭。字符串文本的连续字符(包括 如果有空间或数组大小未知,则终止 null 字符) 初始化 数组的元素。
所以这只是一个快捷方式:
char c[] = {'a', 'b', 'c', '\0'};
像任何其他常规数组一样,可以修改。
c
在其他任何地方:它都会生成:
- 无名
- 字符数组 C 和 C++ 中的字符串文字类型是什么?
- 带静态存储
- 如果修改,则给出 UB
所以当你写的时候:
char *c = "abc";
这类似于:
/* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed;
请注意隐式转换 from to ,这始终是合法的。
char[]
char *
那么如果你修改 ,你也会修改 ,这就是 UB。
c[0]
__unnamed
这记录在 6.4.5 “字符串文字”中:
5 在转换阶段 7 中,每个多字节都会附加一个值为零的字节或代码 由一个或多个字符串文本生成的字符序列。多字节字符 然后,序列用于初始化静态存储持续时间和长度的数组 足以包含序列。对于字符串文字,数组元素具有 键入 char,并使用多字节字符的单个字节进行初始化 序列 [...]
6 没有具体说明这些数组是否是不同的,前提是它们的元素具有 适当的值。如果程序尝试修改此类数组,则行为为 定义。
6.7.8/32 “初始化”给出了一个直接的例子:
示例 8:声明
char s[] = "abc", t[3] = "abc";
定义“普通”char 数组对象,其元素使用字符串文字初始化。
s
t
此声明等同于
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
数组的内容是可修改的。另一方面,声明
char *p = "abc";
使用类型“指向 char”进行定义,并将其初始化为指向类型为“array of char”且长度为 4 的对象,其元素使用字符串文字初始化。如果尝试用于修改数组的内容,则行为是未定义的。
p
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 将其存储在 section 中,而不是 .char*
.rodata
.text
但请注意,默认链接器脚本将 和放在同一段中,该段具有执行但没有写入权限。这可以通过以下方式观察到:.rodata
.text
readelf -l a.out
其中包含:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
如果我们对以下方面做同样的事情:char[]
char s[] = "abc";
我们得到:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
因此,它被存储在堆栈中(相对于 )。%rbp
此外,考虑到,对于只读目的,两者的使用是相同的,您可以通过索引 with 或 format 来访问字符:[]
*(<var> + <index>)
printf("%c", x[1]); //Prints r
和:
printf("%c", *(x + 1)); //Prints r
显然,如果你尝试这样做
*(x + 1) = 'a';
您可能会遇到分段错误,因为您正在尝试访问只读内存。
评论
x[1] = 'a';
char *str = "Hello";
上面将 str 设置为指向在程序的二进制映像中硬编码的文本值“Hello”,该值在内存中被标记为只读,这意味着此 String 文本中的任何更改都是非法的,并且会引发分割错误。
char str[] = "Hello";
将字符串复制到堆栈上新分配的内存中。因此,对它进行任何更改都是允许的和合法的。
means str[0] = 'M';
将 str 更改为“Mello”。
有关更多详细信息,请查看类似的问题:
为什么在写入使用“char *s”而不是“char s[]”初始化的字符串时会出现分段错误?
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
区别的一个例子:
printf("hello" + 2); //llo
char a[] = "hello" + 2; //error
在第一种情况下,指针算术正在工作(传递给函数的数组衰减为指针)。
评论