一个关于char型指针的危险认识(欢迎大家讨论)
小弟最近在浏览论坛内的帖子的时候发现了一个对char型指针不太正确的认识:
首先请看一段代码:
char *stri = "usually";
strncpy(stri,"123",3);
(原贴见:http://expert.csdn.net/Expert/topic/2501/2501487.xml?temp=.2497522)
还不止这一个帖子里面有类似语句:
char *p = "string";
的。
我要指出的是这样做不是不可以,而是非常危险的,首先我们先看看这种语句会产生什么运行效果:
看看对应的汇编
mov dword ptr [ebp-4],offset string "abddddd" (0041fe50)
什么意思呢?就是在程序栈中申请四个字节的指针空间(ebp-4),然后(注意这是最关键的),把数据段里面的字符串首地址付给该指针。于是问题出现了,由于我们程序的数据段是写保护的,那么象我最上面举的例子就肯定不能运行成功了,因为它试图向写保护的内存段拷贝内容。就鉴于此我们才有了const关键字(当然const关键字不止干这一件事情),我们的变量可以这样定义:
const char *p = "string";
这样定义变量说明p指针指向的内容不可以修改,即如果程序这样写:
const char *stri = "usually";
strncpy(stri,"123",3);
那么编译的时候就会出错。
下面我再写一个例子,使各位大哥对我上面的叙述有更深入的了解:
#include "stdio.h"
int main(int argc, char* argv[])
{
DWORD dw;
char *p1 = "abddddd";
VirtualProtect(p1 , 8 , PAGE_READWRITE , &dw);
strncpy(p1 , "ABC" , 3);
VirtualProtect(p1 , 8 , dw , &dw);
printf(p1);
return 0;
}
大家看看这段程序会有什么运行结果?答案是运行不会出错,而且最后会打印"ABCdddd"这个结果,这说明我们也可以通过修改数据段的只读属性来达到修改自己的数据段内容的目的,但是我决不推荐这种用法,太危险了。
下面我们来看看几种正确的用法,这些用法所得到的字符串指针都可以随意操作的(不过可别越了界):
正确用法一:
char stri[] = "usually";
这段语句如何运行呢?见下:
mov eax,[string "usually" (0041f01c)]
mov dword ptr [ebp-8],eax
mov ecx,dword ptr [string "usually"+4 (0041f020)]
mov dword ptr [ebp-4],ecx
我们可以看到,程序首先把字符串"usually"的前四个BYTE,放到ebp-8位置,然后把后四个BYTE放到ebp-4位置,说句通俗的话就是程序在栈里面申请了一个8个字节长的缓冲区,然后把数据段中的内容分批复制到该缓冲区内,可能我们用的字符串刚好7个不太说明问题,下面我们看一个字符串稍长的例子:
char stri[] = "usuallyaabbcc";
对应代码:
mov eax,[string "usuallyaabbcc" (0041fe58)]
mov dword ptr [ebp-10h],eax
mov ecx,dword ptr [string "usuallyaabbcc"+4 (0041fe5c)]
mov dword ptr [ebp-0Ch],ecx
mov edx,dword ptr [string "usuallyaabbcc"+8 (0041fe60)]
mov dword ptr [ebp-8],edx
mov ax,[string "usuallyaabbcc"+0Ch (0041fe64)]
mov word ptr [ebp-4],ax
这段代码不用我解释了吧,总体说就是程序申请了一个16字节的缓冲区,然后把数据段的数据分四次复制过来,顺便说一句为什么申请16而不是14呢,因为我们现在用的32位系统对DWORD的操作最快(^_^,这是编译器的优化呀)。
正确用法二:
char stri[20] = "usuallyaabbcc";
看看代码:
mov eax,[string "usuallyaabbcc" (0041fe58)]
mov dword ptr [ebp-14h],eax
mov ecx,dword ptr [string "usuallyaabbcc"+4 (0041fe5c)]
mov dword ptr [ebp-10h],ecx
mov edx,dword ptr [string "usuallyaabbcc"+8 (0041fe60)]
mov dword ptr [ebp-0Ch],edx
mov ax,[string "usuallyaabbcc"+0Ch (0041fe64)]
mov word ptr [ebp-8],ax
这段代码与上面所述的第二个例子相似,所不同的是我们指定了缓冲区的大小(大小为20,0x14),其它地方一样的,这里不再叙述了。
正确用法三:
char *stri = new char[20];
strcpy(stri , "usuallyaabbcc");
这个例子我不贴汇编代码了,因为很好理解,就是首先在堆中申请空间,然后进行字符串复制。(不要忘记删除呀^_^)
我们前面叙述了多种字符串指针的申请方法都可以,但就是不能这样写:
char *p = "string";
如果非要这样写的话,也一定要写成这样:
const char *p = "string";
虽然第一种写法不会报错,而且还可以进行读操作,但是作为一名严谨的程序员,这种代码是不能出现的,即使知道自己在干什么也不可以,反正作为我个人的编码规则,程序中决不容许这种代码存在。
说到这里我的意思也表达清楚了,小弟拙文漏笔,实不入各位大哥高眼,但有错误之处,望多多指正。