字符串 char *s="hello";
与 char s[]="hello";
,看似都是将 hello 字符串的地址赋值给指针 *p
。
但是前面一个表达式是字符串常量的地址赋值给指针,该指针指向的字符串中的字符是不允许被更改的。
而后面一个表达式是将该字符串的每一个字符赋值给数组,该指针指向的数组的首地址,而数组成员是变量,因此可以允许被更改赋值的。
下面举例说明它们的主要区别,下面的代码:
char *s = "Hello world";
在 C++ 11 以前,上面的将把“Hello world”放在内存的只读部分,并用指针 s 指向它,这使得任何在该内存的写入操作都是非法的。例如,执行下面的代码:
s[1] = '1'; // 错误示范。。。。。。
将会抛出异常信息:
Signal name : SIGSEGV
Signal meaning : Segmentation fault
在 C++ 11 中,char *s = "Hello world";
的做法会产生如下的警告信息:
warning: ISO C++11 does not allow conversion from string literal to 'char *'
所以,不再建议使用这种写法。
如下写法:
char s[] = "Hello world";
会将文本字符串放在只读内存中,并将该字符串复制到堆栈上新分配的内存中。从而使得如下操作:
s[9] = 'k';
s[10] = '!';
能够顺利进行。
以上的 char s[] = "Hello world";
语法也是现行 C++ 标准鼓励的姿势。
扩展一下
虽说,char *s = "Hello world";
不被鼓励,但是下面的语法是可以的。
const char *S1 = "Hello world"; // 编译器表示这可以接受
所以,我们可以使用以下两种方式定义字符串常量:
const char *S1 = "Hello world";
const char S2[] = "Hello world";
下面再看一个例子:
const char* c1;
const char* c2;
{
const char* source1 = "Hello";
c1 = source1;
const char source2[] = "Hi";
c2 = source2;
}
printf("c1 = %s\n", c1); // 输出 Hello
printf("c2 = %s\n", c2); // 在有些情况下,可能不会输出 Hi
source2 是位于一个本地代码块的字符数组,它将在执行完代码块的右大括号之后被销毁。
对于字符文字常量,它具有静态的存储周期。因此,在退出代码块后,指向字符串文字的指针扔将有效。与字符数组的情况相反,字符串文字仍将是可用的。
要考虑的是,在 C 语言中字符串文字“Hello”的类型是 char[6]。任何字符串文字的类型都是非常量字符数组。然而,我们不能改变字符串文字。在 C++ 中,与 C 不同的是,字符文本有常量字符数组的类型。
字符串常量在内存中的生命周期
接下来我们看下由以上这个问题引出来的关于字符串常量在内存中存在生命周期的的问题。
问题如下:
假如有如下的代码(注意,这样的代码在最新的编译器上往往会触发警告)
char *s0="hello";
s0="world";
就是说如果开始 s0 指针指向“hello”这个字符串这个常量的地址,但是当 s0 指针指向了“world”符串的时候,那么“hello”这个字符串在内存中的是否会被释放掉,还是会一直存在内存中,直到程序结束?
我们不妨先试着写几行代码,通过运行结果来进行分析:
#include <stdio.h>
int main() {
char *s0, *s1, *s2;
s0 = "hello";
s1 = "hello";
s2 = "hello";
printf("%0x\n", s0);
printf("%0x\n", s1);
printf("%0x\n", s2);
return 0;
}
其运行输出如下:
34a02b4e
34a02b4e
34a02b4e
可见,三个指针变量的指向的地址都是相同的。
分析:通过运行结果我们可以看出,赋给不同字符指针的相同的字符串,所有的指针都指向了相同的地址。
以上示例由于三个指针变量都在同一个 main 函数中,因此不能看出其生命周期,但是可以为下面的示例解释做好铺垫。
示例二代码:
#include <stdio.h>
char *s0 = "hello";
void a()
{
char *s1 = "hello";
printf("%0x\n", s1);
s1 = "world";
}
void b()
{
char *s2 = "hello";
printf("%0x\n", s2);
s2 = "world";
}
int main()
{
char *s3 = (char *)0;
printf("%0x\n", s0);
a();
b();
s3 = "hello";
printf("%0x\n", s3);
return 0;
}
其运行输出如下:
92969bce
92969bce
92969bce
92969bce
四个字符指针变量指向的地址还是一样的。
分析:这就说明问题了,在函数 a()
,b()
中,局部指针变量*s1
,*s2
都指向“hello”字符串,但是在退出之前有改变指针指向其他字符串,但是两个字符串指针打印出来的地址还是一样的,说明在使用了字符串常量后,该字符串常量并没有在随着函数的结束而消失,而是依旧存在于内存中,因此当其他函数中使用一样的字符串常量时,指向的依旧是跟还是一样的地址。但是也许有人会问到,因为有在全局字符指针变量 char *s0 ="hello";
指向了该字符串常量,会像全局变量一样,在整个程序运行期间都不会释放,因此其他函数调使用该字符串常量时才指向了同一地址。确实有这种可能,那么我们就继续再看下面的示例:
#include <stdio.h>
void a()
{
char *s1 = "hello";
printf("%0x\n", s1);
s1 = "world";
}
void b()
{
char *s2="hello";
printf("%0x\n", s2);
s2 = "world";
}
int main()
{
char *s3 = (char *)0;
a();
b();
s3 = "hello";
printf("%0x\n", s3);
return 0;
}
其运行输出如下:
1232eb8e
1232eb8e
1232eb8e
三个字符串指针变量指向的地址还是一样的。
分析:当我们去掉全局指针变量后,其结果依旧是 *s1
,*s2
,*s3
三个指针变量指向的地址依旧是没有变。在运行完 a()
,b()
两个函数之后,我们再将“hello”赋值给空指针 *s3
指针,其指向地址与 *s1
,*s2
都一样的。
因此我们可以得出结论:一旦有字符串常量在运行期间创建,就会在内存中一直保持到程序结束,当使用相同的字符串常量的时候,不会再创建字符串常量,而是指向之前创建的那个。因此字符串常量是贯穿整个程序的生命周期的。
那么当我们将 char *s0 改为char s0[ ]时,会有什么不同呢?
示例:
#include <stdio.h>
char s0[] = "hello";
void a()
{
char *s1 = "hello";
printf("%0x\n", s1);
s1 = "world";
}
void b()
{
char *s2 = "hello";
printf("%0x\n", s2);
s2 = "world";
}
int main()
{
char *s3 = (char *)0;
printf("%0x\n", s0);
a();
b();
s3 = "hello";
printf("%0x\n", s3);
return 0;
}
其运行输出如下:
d46ff010
d44fdbae
d44fdbae
d44fdbae
s0 与 s1 、s2、 s3 指向的地址是不同的。
分析:造成这样的结果的原因是因为 char s0[] = "hello";
这句代码的运行意义是如果 "hello"
这个常量不存在,会将文本字符串放在只读内存中,则(如果 "hello"
这个常量存在,则仅)将该字符串复制到堆栈上新分配的内存中,即将每一个字符赋值给数组。*s0
指向的是该数组的首地址,而不是字符串的首地址。而 *s1
、 *s2
、 *s3
都是指向只读内存中字符串的首地址,因此它们是不同的。
本文非原创,感谢原作者的付出,参考链接如下:
网友评论