[文章] 有关char指针的文章一篇
先看以下代码:
char *p;
p="abc";
你认为是对的吗?
答案:语法上是对的,但不提倡这种写法。
误区1:没有给p分配内存空间就赋值,怎么会是对的呢?
正解:不少人第一眼将这里的p="abc"看成了*p="abc",然后就做出了以上的论断。这是比较笨笨的错误咯:)
看清楚就好啦,其实赋给p的是"abc"的地址。再说,*p="abc"也不对呀,字符串可不能这么赋值。
误区2:这"abcd"哪来的地址,怎么能直接赋给p呢?
正解:先自己试试吧。在2K/XP + VC下运行这段代码,是不会出错的,说明这段代码并无问题。晕吧?猜想的话呢,就是"abcd"不知道被放在了什么地方,然后弄来了一个地址,给了p。
这到底是怎么回事呢?
要知道,这两个语句和char *p="abc"是完全一样的,所以其中的道理也一样。
char *p="abc"曾经迷惑了不少人呀。问问你:p到底是什么类型的?char *?错,是const char *!
也就是说,它所指向的内容是不可改变的。不过要补充的是,a的指向是可以改变的。
所以为了不再引起误会,char *p="abc这种写法是不提倡的。
既然char *p="abcd"被建议写成const char *p="abcd",那么char *p; p="abcd";也应该写成const char *p; p="abcd";
讲来讲去,最后来得看看汇编代码。看完就明白是怎么回事了。(我才发现汇编代码原来这么爽看!VC下,没开编译器优化):
我们重点先看const char *p="abc"和char p[]="abc"有什么不同(都放在main()中声明):
PHP源码:
void main()
{
const char *p="abc";
}
3: const char *p="abc";
00401028 mov dword ptr [ebp-4],offset string "abc" (0041f01c)
void main()
{
char p[]="abc";
}
3: char p[]="abc";
00401028 mov eax,[string "abc" (0041f01c)]
0040102D mov dword ptr [ebp-4],eax
看出差别了吗?上一段ASM用offset取"abc"的地址,然后赋给[ebp-4],也就是p(下同)。
而下一段ASM却转了一个弯,先把"abc"的地址转到寄存器eax,然后再转赋给p。
有疑问了:为什么不和上面的一样,直接用offset?VC是很聪明的(废话,M$的东西呀),不用offset,恐怕就是用不了了。
offset是一条伪指令,在编译的时候就已经把偏移量算好了。offset是无法执行间接寻址的计算的。
说明了什么?
const char *p="abc"中的"abc",在编译期间就已经处理好,要了一块内存,存起来了!在把地址赋给p的时候,就可以直接用offset计算。
而char p[]="abc"中的"abc",是在运行期间动态分配内存给"abc",然后再算出地址,赋给p。hehe,这同时也说明了数组和指针的等价性。
我们再做一次实验,这一次我们把char p[]="abc"放在main()外,并在main()内用一个指针再指向p看看。
PHP源码:
char p[]="abc";
void main()
{
char *t=p;
}
5: char *t=p;
00401028 mov dword ptr [ebp-4],offset p (00421adc)
这回p[]的声明放在main()外,变成了全局变量。结果main()内的t取p的指针的时候,直接用offset可以计算出来。
据小石头所说,字面值,const,static,inline,全局变量都是放在静态数据区的。(但我感觉似乎不是如此,只是全局变量和字符串在编译时就处理好放在一起)
不难发现,p[]被声明成全局变量后,就可以直接用offset计算地址,说明静态数据区是编译时就已经处理好的。
再对照const char *p="abc"的ASM,我们马上想到:"abc"就是被C/C++存在静态数据区中的!它的地址就是"abc"在静态数据区的地址!
弄清了这个,有些问题也就可以想得通了。下面这种用法,看来不能说是错误的了,因为"abc"是在静态数据区的,生存期可以说是整个程序:
PHP源码:
#include "stdio.h"
const char *fun()
{
const char *p="abc";
return p;
}
void main()
{
const char *t=fun();
printf ("%s",t);
}
看ASM:
PHP源码:
5: const char *p="abc";
00401038 mov dword ptr [ebp-4],offset string "%d" (0042001c)
一样也是使用offset计算地址。
C/C++给字符串的待遇真是太好了。为了一个字符串,几乎可以打破所有的指针规则。晕~~~~(完)
附:第一次写这么长的文章,写得挺晕的。本来我的C/C++也不是很纯熟的,ASM也是一知半解,今天在CSDN上为这个问题郁闷了半天,和几个人讨论了一下,最后就写了这么一篇文章。希望大家赏脸看看~~~有错一定要指正啊!最后特别感谢小石头(想飞的菜鸟,骄傲的石头,菜菜,都是他咯),还有小阿哥(就是Kingzeus咯)的帮忙~~~~~~谢谢咯~~~~~~
__________________
小菜虎 -> 菜菜的老虎
骄傲的石头回复:
堆几盘积木,心情好些了,所以再重新写一遍。
关于字符串的这个问题,我一直在心里困惑着。所以呢,昨天就看了一下。
以前回答别人的时候,总是很简单的回答,字符串就是const char *指针,指向它的入口地址。现在想来真是惭愧,虽然这个事实好象已经为大家所接受,甚至没有人探讨过这个问题!所以我相信我的发现对大家大多数是有好处的。
首先请看以下代码
PHP源码:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
const char *p = 0;
char *p2 = p;
return 0;
}
以上代码有问题吗? 如果你说没有,请你试一下。很明显,这是有问题的。const是为了保证不变性,而你把他变成non const,肯定有错误或者警告,要么就要用const_cast转换。
所以上面的代码不能通过编译。
那么这就很明显的在lee的post里出现了问题,当然我以前也一直是这么认为,甚至很多人都是这么认为。难道这是编译器对字符串的特殊处理吗? 还是其他的原因?
于是我想看看究竟。就动用了RTTI,我飞快的键入了以下代码。
PHP源码:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout << typeid("abc").name() << endl;
return 0;
}
你说结果是什么?
是char [4]!而不是const char *;
好,这个结果解决了我心中的疑点,原来是这样!这可以很简单的解释char *p = "abc"这个问题。 数组是一个char *const 指针,当然可以赋给char *指针而不会影响其常量性。所以这是完全正确的赋值。
其实这想起来也很平常,指针是没有分配空间的地址而已,而数组是一种容器,占用连续的储存空间。想想字符串就该知道它是一个数组!而不是指针!真正意义上的指针只能是地址,而它在分配了连续的空间后可以作为数组来使用,这是由于他们的共性而决定的。
哈哈!心情愉快,所以也接着看了下lee上面所做的探讨。字符串是放在静态储存区没错,毫无疑问。至于位置~ 偶不想多说,lee在上面分析了很多。所以我只对它进行了一下简单的测试。
我手头上只有dev c++ 和 vc70编译器,所以就只用他们进行了测试。(打开了全部优化)
PHP源码:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
char *p = "abc"
char *p2 = "abc";
p[1] = 'k';
p2[1] = 'j'
cout << p1 << endl;
cout << p2 << endl;
return 0;
}
结果呢?在dev c++下报错, 原因我想可能是因为在dev c++在对静态储存区进行了保护。而vc70下,通过,并且两个输出是不同的。所以偶去看vc70产生的msil码,原来vc70每次处理字符串都在静态区分配了空间,而且这两个"abc"是连续分配的!这里就没有出现Solmyr说的那种情况。我想一般比较好的编译器也应该这么说。至于vc60,偶没有测试,但是可能Solmyr说的情况会出现吧。但这样是不好的,相信这样会出现很多微妙的情况。
所以,引用字符串的最佳格式是const char *指针,但char *指针是完全没错的。于情于理,也说的过去吧
。如果你还要问,那用指针接受数组呢? 呵呵,你该仔细看看前面了。相信这会给你帮助。
__________________
不可一日无酒无肉无女人