[C]char*和char[]的区别
一、概述
在偶然一次尝试使用unix函数mkdtemp的时候发现一个问题,函数有一个唯一参数template是一个字符串指针,它的声明如下:
char *mkdtemp(char *template);
当我声明一个指针形式的字符串指针,并传入mkdtemp的时候,出现了Segmentation fault的报错,代码如下:
#include <stdio.h> #include <stdlib.h> int main(int argc, char const *argv[]) { char *path = "/documents/temp_XXXXXX"; printf("mkdtemp: %s\n", mkdtemp(path)); }
如果把声明改成字符串数组的形式,代码则可以顺利运行:
#include <stdio.h> #include <stdlib.h> int main(int argc, char const *argv[]) { char path[] = "/documents/temp_XXXXXX"; printf("mkdtemp: %s\n", mkdtemp(path)); }
二、原因
通过百度发现了一篇这样的文章:char*和char[]的区别
这篇文章的开头有一段关于进程内存空间分配的描述;
接下来是一段程序,每个声明都标明了程序会在哪个内存段保存变量或常量:
//main.cpp int a=0; //全局初始化区 char *p1; //全局未初始化区 main() { int b;栈 char s[]="abc"; //栈 char *p2; //栈 char *p3="123456"; //123456\0在常量区,p3在栈上。 static int c=0; //全局(静态)初始化区 p1 = (char*)malloc(10); p2 = (char*)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1,"123456"); //123456\0放在常量区,编译器可能会将它与p3所向"123456"优化成一个 地方。 }
看到对于p3的描述,就能猜测到问题出在哪里了:
在编译阶段,如果把一个类型为指针的变量的初始化值声明成常量字面量,那么这个指针指向的常量空间是不可以修改的!因为常量字面量会被以只读的方式创建在静态存储区(属于C程序存储结构中的正文段),然后只是把其地址赋值给类型为指针的变量。
p3保存的是指向常量区的内存空间,这段内存是不可以修改的。
而对于变量s的写法,编译器会把它当做数组声明处理,创建一个数组,其初始化值是"abc"。
所以对于s来说,用户空间是可以随时修改它的内容的。
以上规则,无论是对于静态存储区(data,bss)或栈区,都是适用的:
#include <stdio.h> #include <stdlib.h> char path[] = "/documents/temp_XXXXXX";//这次把path变量放在静态存储区 int main(int argc, char const *argv[]) { printf("mkdtemp: %s\n", mkdtemp(path));//函数一样能正确运行 }
再来看看mkdtemp的描述:
mkdtemp传入一个字符串路径,这个字符串路径必须以XXXXXX结尾,返回一个字符串路径,这个字符串后6位XXXXXX字符被替换成了在这个路径下唯一的值(因为它在这个路径下是唯一的,所以相对于整个文件系统来说也是唯一的)
猜测是函数通过修改传入的字符串内存空间,然后返回指向该内存空间的指针,由于用字符串常量声明的字符串指针path指向的是字符串常量空间,空间不可以被修改,所以程序报错了。
为了证实这个猜想,打印一下path的值和mkdtemp返回的指针值就行了:
#include <stdio.h> #include <stdlib.h> int main(int argc, char const *argv[]) { char path[] = "/documents/temp_XXXXXX"; printf("ptr addr with stack: %p\n", path); printf("ptr addr by mkdtemp return: %p\n", mkdtemp(path)); }
结果:
ptr addr with stack: 0x7fffa7440a00 ptr addr by mkdtemp return: 0x7fffa7440a00
看吧,一模一样的;
三、解决方法
遇到需要修改指向空间的函数,不要声明成一个指针指向常量区的指针。
那么怎么识别函数是否需要修改指针指向的空间呢?
其实,通常看函数声明就知道这个函数内部需不需要修改参数指针指向的空间;
就好像mkdtemp一样,需要修改参数指针指向空间的参数声明是不带类型限定符const的:
char *mkdtemp(char *template);
而不需要改变参数指针指向空间的函数参数通常会声明成这个样子:
FILE *fmemopen(void *restrict buf, size_t size,const char *restrict type);
type参数被声明成了常量,所以可以放心把字符串写在只读区;
四、相关文献
在《C语言核心技术》的"第三章 常量 字符串常量"最后一段中提到这个问题;
在《Unix高级环境编程》的第 5.13 小节的最后也有一个这个问题的示例和说明;