今天我在写二叉树程序时遇到一个让我郁闷整晚的例子,现在终于想通了,特撰此博文,告诫自己及广大同道今后勿犯此错误!
我写了一个将字符串中括号去除的程序:(转载请注明出处:Grim_Rapier:http://blog.csdn.net/pq159753159/article/details/17342999)
#include<stdio.h>
char *RidBracket(char*);
void main()
{
char *sOldBiTree="qwe(123)";
char *sBiTree=RidBracket(sOldBiTree);
puts(sBiTree);
}
char *RidBracket(char *pStr)
{
char *pRslt=pStr;
int i=0,j=0;
for(i=0;pStr[i]!=0;i++)
if(pStr[i]!='(' && pStr[i]!=')')
pRslt[j++]=pStr[i];
pRslt[j]=0;
return pRslt;
}
此程序乍一看很精简也很正常,但运行后便会出现“非法访问内存的错误”。我仔细推敲,甚至将其反汇编后检查它的汇编代码,仍不理解错在何处?
细心的读者会发现我在RidBracket函数体内的第一句话是:直接将原字符串地址赋给新指针。本来打算利用此语句既实现分配空间,又确保不浪费
太多空间的效果,没想到适得其反。赋值后,两个指针同时指向同一空间,但程序第一次运行到 pRslt[j++]=pStr[i];就异常中止了。但错误之根本并不在于此!
后来,我又经过反复测试并研究其汇编代码,终于找到了问题的关键(虽然当时熬到凌晨,困,但兴奋)。为深刻阐述,再举个更简单的例子:
考虑这两段程序:
1.
#include<stdio.h>
void main()
{
char p[9]="12345";
p[2]='2';
puts(p);
}
2.
#include<stdio.h>
void main()
{
char *p="12345";
p[2]='2';
puts(p);
}
都能编译通过,但只有程序1能正确运行,原因何在?
先看其汇编代码:
1.
char p[9]="12345";
0040D9E8 mov eax,[string "1234" (00422f98)]
0040D9ED mov dword ptr [ebp-0Ch],eax
0040D9F0 mov cx,word ptr [string "1234"+4 (00422f9c)]
0040D9F7 mov word ptr [ebp-8],cx
0040D9FB xor edx,edx
0040D9FD mov word ptr [ebp-6],dx
0040DA01 mov byte ptr [ebp-4],dl
2.
char *p="12345";
0040D9E8 mov dword ptr [ebp-4],offset string "1234" (00422f98)
char *p="12345";的汇编比较简单,而问题的关键就在于此!
我简单翻译一下它们的含义:
汇编1:在主调函数的栈空间上腾出9字节空间(确切为12byte)存储字符串“1234”;
汇编2:直接将内存00422f98处定义的字符串“1234”的地址即00422f98本身保存在主调函数空间内
当我们访问字符串时,两种方法都可以将其打印输出;对于主调函数空间中存储的数据可以读写;对于堆中(即其它内存块)中的数据,由于是在程序申请的执行空间中定义的
故只能读不能覆盖,如果重写之,则等于改写了原程序,破坏了原程序的状态(也可以解释为改写了程序栈空间外的内存,这是操作系统所不允许的)。
因此,由于程序1是在主调函数中的栈空间操作数据,程序2试图改写堆中的数据,只有程序1能正确运行。
再看本文开头的程序main函数的第一句是:char *sOldBiTree="qwe(123)";而被调函数RidBracket试图对"qwe(123)"的地址进行写操作,这是不允许的。
此类错误一般极难发现,因此,在今后的编程中,应尽量采用char p[9]="12345";的方式定义字符串!
(转载请注明出处:Grim_Rapier:http://blog.csdn.net/pq159753159/article/details/17342999)