最近在项目中遇到一个很有意思的问题。简单重现一下,大概是如下情况。
在文件f1.cpp中定义字符串
char str[16] = "abcdefg";
在其他文件中,比如f2.cpp中使用extern进行一下声明
extern char *str;
在对str进行操作时,比如最简单的
void func(){ cout << str << endl;}
这时候竟然出现了"段错误"Segmentation fault
其实单抓出来我们都能发现问题,但是在一个庞大的项目中,这种细枝末节并没那么显眼(当然这个代码不是我写的,坑不是我挖的,我不管就是不是我的错,逃)
分析
上面的问题其实很简单,在f2.cpp中str
被声明为了char *
,而在str
被定义的f1.cpp中,str
的类型是char[]
。这就是问题所在了。
可能很多人会忽略这种小问题,或者"觉得char[]
和char*
有什么区别?不都一样用吗?"
虽然大多数情况下,在同一个文件中对char[]
和char*
操作好像没有什么不同,
char str[16] = "aaa";cout << arr << endl;//传参等等
那是因为在同一个文件中,如果你对变量名操作,编译器会直接取用其地址而让你产生错觉,不过我们也能看出端倪
char str[16] = "aaa";char *pstr = str;//32位sizeof(str); //16sizeof(pstr); //4
回到刚才的问题,那么为什么在同一个文件中很正常的操作到了跨文件时就出现了异常呢?
其实很简单,我们调试一下就知道,这里我用的是VisualStudio,
在同一个文件中时str 0x0034f988 "aaa" char[16]pstr 0x0034f988 "aaa" char *
可以看到我们定义pstr时是直接正确的将str这个字符串的地址给赋值给了pstr,这一切就很合理了。
这时候我们再回来看在不同文件时的情况str 0x64636261 "读取字符串时出错" char*
地址一看就不对,字符串常量值应该在只读常量区的,这么高的地址肯定是错的,但是为什么会出现这个地址呢?仔细看一下,这个地址有点奇怪64 63 62 61?
看一下ASCII表,16进制对应的字符分别是d c b a
, 但是为什么会取到这个值呢?
这里就牵扯到编译的问题了,编译器在编译文件f1.cpp的时候,将str名字及其地址加入符号表;
在编译文件f2.cpp的时候,由于str
只是声明,而符号表中确实能找到str
及其地址,编译链接顺利通过没有任何问题。但是到了运行的时候,再f2.cpp中使用str
的名字进行寻址,从而顺利找到f1.cpp中str
的首地址,但是问题出现了,这里我们声明的str
类型是char *
,这个指针的值是什么呢?
指针变量的地址有了,值当然就是这个地址开始取4个字节的值了,结果顺利取到x64636261
。在f2.cpp中把声明类型改为extern char str[];
就没有任何问题了。
也许有人会问“既然如此,地址不应该是0x61626364吗?”
关于这个问题的话,,因为我的机器是小端的
到这里这个问题的缘由就解释清楚了,也警示了我们平时开发过程中对细节的重视要加强,这里再次重申
[]类型和*类型不是同一种类型!!!!!!!