char *p = "hello"
这是一个指向以h开头的字符数组的字符指针。
char p[] = "hello"
这是一个存储hello的数组。
当我将这两个变量都传入此函数时,有何区别?
void printSomething(char *p)
{
printf("p: %s",p);
}
char*
和char[]
是不同类型,但在某些情况下并不立即明显。这是因为数组会退化为指针,也就是说,如果提供了一个char[]
类型的表达式,而期望的是char*
类型,则编译器会自动将数组转换为指向其第一个元素的指针。
你的示例函数printSomething
期望传入一个指针,所以如果你像这样尝试将一个数组传递给它:
char s[10] = "hello";
printSomething(s);
编译器会假装你写了这个代码:char s[10] = "hello";
printSomething(&s[0]);
printf
函数会按照 %s
格式化字符串的方式进行处理:从提供的地址开始并一直打印直到遇到 null 终止符。如果想要只打印一个字符,可以使用 %c
格式化字符串。 - jacobqchar *p = "abc";
这个语句中是否会自动添加空字符 \0
,就像 char [] 数组一样? - ajaysinghnegichar *name; name="123";
,但是不能使用 int
类型做同样的事情?并且在使用 %c
打印 name
后,输出是无法读取的字符串:�
? - TomSawyer让我们来看看:
#include <stdio.h>
#include <string.h>
int main()
{
char *p = "hello";
char q[] = "hello"; // no need to count this
printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both
// size_t strlen(const char *s) and we don't get any warnings here:
printf("%zu\n", strlen(p)); // => 5
printf("%zu\n", strlen(q)); // => 5
return 0;
}
foo*和foo[]是不同的类型,编译器会以不同的方式处理它们(指针=地址+指针类型的表示,数组=指针+数组长度(如果已知),例如,如果数组是静态分配的),详细信息可以在标准中找到。在运行时级别上,它们没有区别(在汇编语言中,几乎没有区别,见下文)。
Q: What is the difference between these initializations?
char a[] = "string literal"; char *p = "string literal";
My program crashes if I try to assign a new value to p[i].
A: A string literal (the formal term for a double-quoted string in C source) can be used in two slightly different ways:
- As the initializer for an array of char, as in the declaration of char a[] , it specifies the initial values of the characters in that array (and, if necessary, its size).
- Anywhere else, it turns into an unnamed, static array of characters, and this unnamed array may be stored in read-only memory, and which therefore cannot necessarily be modified. In an expression context, the array is converted at once to a pointer, as usual (see section 6), so the second declaration initializes p to point to the unnamed array's first element.
Some compilers have a switch controlling whether string literals are writable or not (for compiling old code), and some may have options to cause string literals to be formally treated as arrays of const char (for better error catching).
See also questions 1.31, 6.1, 6.2, 6.8, and 11.8b.
References: K&R2 Sec. 5.5 p. 104
ISO Sec. 6.1.4, Sec. 6.5.7
Rationale Sec. 3.1.4
H&S Sec. 2.7.4 pp. 31-2
初始化 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 "初始化" 给出了一个直接的例子:
EXAMPLE 8: The declaration
char s[] = "abc", t[3] = "abc";
defines "plain" char array objects
s
andt
whose elements are initialized with character string literals.This declaration is identical to
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
The contents of the arrays are modifiable. On the other hand, the declaration
char *p = "abc";
defines
p
with type "pointer to char" and initializes it to point to an object with type "array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to usep
to modify the contents of the array, the behavior is undefined.
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
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
您不允许更改字符串常量的内容,这就是第一个p
所指向的内容。第二个p
是用字符串常量初始化的数组,您可以更改其内容。
char *p = "hello";
char p[10] = "hello";
来自APUE,第5.14节:
char good_template[] = "/tmp/dirXXXXXX"; /* right way */
char *bad_template = "/tmp/dirXXXXXX"; /* wrong way*/
mkstemp
函数尝试修改字符串时,会发生段错误。p[1]== *(&p+1)
这是一个正确的声明。
*(arr + 1)
可以让你访问到 arr
的第二个成员。如果 *(arr)
指向一个32位的内存地址,例如 bfbcdf5e
,那么 *(arr + 1)
就指向 bfbcdf60
(第二个字节)。这也是为什么如果操作系统没有发生段错误,超出数组范围会导致奇怪的结果。如果 int a = 24;
存储在地址 bfbcdf62
,那么访问 arr[2]
可能会返回 24
,前提是没有发生段错误。 - Braden Bestchar p[3] = "hello"
?应该是char p[6] = "hello"
,请记住在C语言中,“字符串”末尾有一个'\0'字符。
无论如何,在C语言中,数组只是指向内存中相邻对象的第一个对象的指针。唯一的区别在于语义。虽然您可以更改指针的值以指向内存中的不同位置,但创建后,数组将始终指向同一位置。
此外,在使用数组时,“new”和“delete”会自动完成。
char p[3] = "hello";
初始化字符串对于你声明的数组大小来说太长了。打错字了吗? - Cody Graychar p[]="hello";
就足够了! - deepdivechar
特定的。 - Ciro Santilli OurBigBook.com