在C语言中,可以在这样的声明中使用字符串字面值:
char s[] = "hello";
或者像这样:
char *s = "hello";
那么有什么不同呢?我想知道在编译和运行时,在存储持续时间方面实际发生了什么。
在C语言中,可以在这样的声明中使用字符串字面值:
char s[] = "hello";
或者像这样:
char *s = "hello";
那么有什么不同呢?我想知道在编译和运行时,在存储持续时间方面实际发生了什么。
当前回答
另外,考虑到对于只读目的,两者的使用是相同的,您可以通过使用[]或*(<var> + <index>)索引来访问一个char。 格式:
printf("%c", x[1]); //Prints r
And:
printf("%c", *(x + 1)); //Prints r
很明显,如果你试图这么做
*(x + 1) = 'a';
你可能会得到一个分割错误,因为你试图访问只读内存。
其他回答
此声明:
char s[] = "hello";
Creates one object - a char array of size 6, called s, initialised with the values 'h', 'e', 'l', 'l', 'o', '\0'. Where this array is allocated in memory, and how long it lives for, depends on where the declaration appears. If the declaration is within a function, it will live until the end of the block that it is declared in, and almost certainly be allocated on the stack; if it's outside a function, it will probably be stored within an "initialised data segment" that is loaded from the executable file into writeable memory when the program is run.
另一方面,这个声明:
char *s ="hello";
创建两个对象:
一个6个字符的只读数组,包含值'h', 'e', 'l', 'l', 'o', '\0',它没有名称,具有静态存储持续时间(意味着它存在于程序的整个生命周期中);而且 一个类型为指针到字符的变量,称为s,用该未命名只读数组中第一个字符的位置初始化。
未命名的只读数组通常位于程序的“文本”段中,这意味着它与代码本身一起从磁盘加载到只读内存中。s指针变量在内存中的位置取决于声明出现的位置(就像在第一个例子中一样)。
举个例子来说明区别:
printf("hello" + 2); //llo
char a[] = "hello" + 2; //error
在第一种情况下,指针算术是有效的(传递给函数的数组衰减为指针)。
c99n1256草案
字符串字面量有两种不同的用法:
Initialize char[]: char c[] = "abc"; This is "more magic", and described at 6.7.8/14 "Initialization": An array of character type may be initialized by a character string literal, optionally enclosed in braces. Successive characters of the character string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array. So this is just a shortcut for: char c[] = {'a', 'b', 'c', '\0'}; Like any other regular array, c can be modified. Everywhere else: it generates an: unnamed array of char What is the type of string literals in C and C++? with static storage that gives UB if modified So when you write: char *c = "abc"; This is similar to: /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; Note the implicit cast from char[] to char *, which is always legal. Then if you modify c[0], you also modify __unnamed, which is UB. This is documented at 6.4.5 "String literals": 5 In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals. The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char, and are initialized with the individual bytes of the multibyte character sequence [...] 6 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.
6.7.8/32“初始化”给出了一个直接的例子:
EXAMPLE 8: The declaration char s[] = "abc", t[3] = "abc"; defines "plain" char array objects s and t 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 use p 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
结论:GCC将char* it存储在.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)。
根据这里的注释,应该很明显:char * s = "hello"; 是一个坏主意,应该在非常狭窄的范围内使用。
这可能是指出“const正确性”是一件“好事”的好机会。无论何时何地,你可以使用“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没有运行时空间或速度损失。
(当然,假设你使用的是c++编译器?)
——日本
给出声明
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
字符串字面值"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重新赋值以指向不同的数组。