在C语言中,可以在这样的声明中使用字符串字面值:
char s[] = "hello";
或者像这样:
char *s = "hello";
那么有什么不同呢?我想知道在编译和运行时,在存储持续时间方面实际发生了什么。
在C语言中,可以在这样的声明中使用字符串字面值:
char s[] = "hello";
或者像这样:
char *s = "hello";
那么有什么不同呢?我想知道在编译和运行时,在存储持续时间方面实际发生了什么。
当前回答
给出声明
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重新赋值以指向不同的数组。
其他回答
举个例子来说明区别:
printf("hello" + 2); //llo
char a[] = "hello" + 2; //error
在第一种情况下,指针算术是有效的(传递给函数的数组衰减为指针)。
根据这里的注释,应该很明显: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 *s = "Hello world";
将“Hello world”放置在内存的只读部分,并将s作为指向它的指针,使得对该内存的任何写入操作都是非法的。
虽然做的事情:
char s[] = "Hello world";
将字面值字符串放在只读内存中,并将字符串复制到堆栈上新分配的内存中。从而使
s[0] = 'J';
合法的。
另外,考虑到对于只读目的,两者的使用是相同的,您可以通过使用[]或*(<var> + <index>)索引来访问一个char。 格式:
printf("%c", x[1]); //Prints r
And:
printf("%c", *(x + 1)); //Prints r
很明显,如果你试图这么做
*(x + 1) = 'a';
你可能会得到一个分割错误,因为你试图访问只读内存。
首先,在函数参数中,它们是完全等价的:
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()或类似的方法进行复制。如果数组是在全局范围内分配的,当然没有问题。