转自:http://blog.csdn.net/lzpaul/archive/2008/09/05/2884095.aspx
一个简单的字符串相关程序,运行时崩溃,罪魁祸首 char *str 和 char str[]的不同之处
代码如下:
上述代码编译链接后,运行时程序崩溃,崩溃??!!
仔细分析了这两个函数,没发现有什么不对。
疯了,我也快蓝屏了,: (
定一下,进入debug
当运行到上面 //Stop!处出错 Unhandled exception in test.exe:0xC0000005:Access Violation
停在汇编指令 mov byte ptr [edx],cl 处,说明执行该指令出错,(将寄存器值写内存时出错,也即将* (p+1)的值写到*p 时候出错,该指令的上一指令是 mov cl,byte ptr [eax+1] ,将* (p+1)写入寄存器)
用WinDbg进一步分析
同样执行到上述那行代码后停止(Access violation)
(在指令mov byte ptr [edx],cl )
写内存错!!??奇怪!
看下内存中的内容:
0046f02e 63 63 64 00 ccd
在偏移地址0046f02e处开始四个字节分别是63 63 64 00 就是字母ccd的ASCII值,就是主函数字符串"abccd"中内容
没有问题啊?
用windbg的命令查看下这块内存信息,如下
!address edx
00400000 : 0046f000 - 00008000
Type 01000000 MEM_IMAGE
Protect 00000002 PAGE_READONLY
State 00001000 MEM_COMMIT
Usage RegionUsageImage
FullPath test.exe
显示Protect 00000002 PAGE_READONLY,更奇怪了!? 内存只读!
*p = * (p+1); //p所指向的内存位置只读!
把这个问题拿到群里跟别人讨论,出现同样的错误,对方给了另外个版本:
在deletechar()函数中用malloc动态分配一块内存将str所指向的字符串复制到这块内存中,运行OK
这样改动是个办法,但,是不是成本太高了???
说到动态分配,突然想到(就是突然想到了) [ ],改成这个:char str[]试试看,编译,链接,运行,OK!
怪了,怎么这样就好了??!!
有点意思,看来还得往里走走,char *str,char str[ ] 差别还挺大
反汇编!看看汇编怎么做的
于是在main()里就写了两行代码
char *s="abc";
char str[]="abccd";
看看怎么回事
char *str="abc"; 的汇编代码
mov dword ptr [ebp-4],offset string "abc" (0046f034)
char str[ ]="abccd"; 的汇编代码
mov eax,[string "abccd" (0046f02c)]
mov dword ptr [ebp-0Ch],eax
mov cx,word ptr [string "abccd"+4 (0046f030)]
mov word ptr [ebp-8],cx
乍一看,不一样!行数不一样,嘿嘿。开个玩笑
有意思,继续分析
看着这几行代码半天,没看明白不一样在哪里(行数除外 :D )
mov dword ptr [ebp-4],offset string "abc" (0046f034) 是把"abc"的偏移量(指针)给了指针变量str,即str中的值是0046f034
下面这几行干嘛了?
看看内存,寄存器怎么变化的
mov eax,[string "abccd" (0046f02c)]
把abcc四个字符存到寄存器eax中,然后写到内存[ebp-0ch](我的机器上[ebp-0ch]是0013ff74(偏移量),str是偏移量为0013ff74的内存开始处)。
再看看上面的mov指令,是取偏移量(mov dword ptr [ebp-4],offset string "abc" (0046f034)),这里是把串"abccd"所在内存中的值复制到str开始的内存处。
到这里,问题开始清楚了:
char *s="abc";
char str[]="abccd";
1、char *s="abc";
看这个赋值:
右边,是"abc",是个字符串常量,存在于内存某处(我的机器上是ds:0x0046f034),程序员不知道,编译器安排的,也没必要知道(当然,这个赋值之后,程序员就知道并能控制这个串了)。字符串常量所在内存是只读的。
左边,字符指针s,赋值时候,把地址ds:0x0046f034的偏移地址("abc"所在),存放到指针变量s(其地址为 ds:0x0046f034)中。程序员能完全控制的内存只是指针变量所占据的这四个字节内存,只能改变该指针的指向,至于其指向的内存能不能写,那就看程序了,本程序是指向的只读内存,不能写!
2、char str[]="abccd";
再看这个赋值:
右边,和上面类似,是"abccd",也是个字符串常量,存在于内存某处(是ds:0x0046f02c),程序员不知道,编译器安排的(这个赋值之后,程序员还是不知道这个常量在哪里,因为并没有用指针指向这块内存,这和上面不同)。该字符串常量所在内存也是只读的。
左边,字符数组str,赋值时候,把地址ds:0x0046f02c("abccd"所在)所指内存中的内容,复制到字符数组str开始(其地址为 ds:0x0013ff74)的内存中,每复制一个字符都会开辟一个字节(char型变量占1字节)内存来存放这个字符(这也是实现了数组元素个数的动态确定)。从字符数组str开始的这部分存放这些字符的内存是程序员可以完全控制的,可读写,因此在这些内存写当然是没有问题的!
到这里,突然想到多年前看过的一本书《C和指针》(Kenneth A. Reek著,徐波 译,现在有第二版了¥65,第一版¥55,不便宜,但是是好书,值得研读,是研读哦,不是翻翻,当然翻翻也会有收获的)里面好像提到过这个问题,赶快拿来看看(字符数组的初始化一节),果然这样。 o(∩_∩)o
另附, 数组名和指针的区别与大家分享下
原文http://dmacy.bokee.com/4728674.html
许多程序员对数组名和指针的区别不甚明了,他们认为数组名就是指针,而实际上数组名和指针有很大区别,在使用时要进行正确区分,其区分规则如下:
规则1 数组名指代一种数据结构,这种数据结构就是数组;
char str[10];
char *pStr = str;
cout << sizeof(str) << endl;
cout << sizeof(pStr) << endl;
输出结果为:
10
4
这说明数组名str指代数据结构char[10]。
规则2 数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
char str[10];
char *pStr = str;
str++; //编译出错,提示str不是左值
pStr++; //编译正确
规则3 指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址;
规则4 数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
例如:
void arrayTest(char str[])
{
cout << sizeof(str) << endl; //输出指针长度
str++; //编译正确
}
int main(int argc, char* argv[])
{
char str1[10] = "I Love U";
arrayTest(str1);
return 0;
}