1.问题
char * str="Astring"; strrev(str);
上面这两行代码,第一眼感觉是没有错的,而且MSDN里面也是这样写的。编译能够通过,但是运行起来就会报错,提示Access Violation(访问越界)。
MSDN里面的代码精简一下大致是这样:
/*++ cstr.cpp * *version:1.1 *created:2011-08-14 21:30 *purpose:show AV error * --*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <tchar.h> void _tmain() { _TCHAR* str=_T("Astring"); _tprintf(_T("%s reversed is '%s'\n"),str,_tcsrev(str)); system("pause"); }
这个例子在msdn中本来是用来说明tchar.h对generic-text的支持,然而在使用_tcsrev()对str进行反转时,却出现了Access Violation错误。
仔细再查看一遍sample代码,发现它的后缀名是.c,而笔者编译时是.cpp,问题是否出在这里? 修改上面的文件为cstr.c,再编译,编译成功,可是运行起来,问题依旧。
2.分析
编译能够通过,但是执行时却报告access violation错误,应该是访问了不该访问的东西。另外,vs2010明确了错误就来自_tcsrev(str)。
是不是str不允许被更改?因为在初始化时,它的右边是文字常量,可以看作是const char*类型。问题的原因可能就在这里。通过实验来验证,修改代码:
/*++ cstr.c * *version:1.2 *created:2011-08-14 21:30 *purpose:avoid access violation change str from char* to char[] * --*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <tchar.h> void _tmain() { _TCHAR str[]=_T("Astring"); _tprintf(_T("%s reversed is '%s'\n"),str,_tcsrev(str)); system("pause"); }
把str从_TCHAR*类型换成_TCHAR[]类型,编译成功,运行起来也不报错,但是结果却是这样的:
gnirtsA reversed is 'gnirtsA'
请按任意键继续. . .
这里牵扯出_tprintf()中参数的传入顺序问题,从上面的结果看,是按照从右到左的顺序。(如果对此不感兴趣,直接转看第3节。)
实际上,wprintf是被__cdecl修饰的,而__cdecl这种调用约定和__stdcall一样,函数参数传入顺序从右向左。为了让程序的结果更加符合要求,只需将_tprintf(_T("%s reversed is '%s'\n"),str,_tcsrev(str));修改为_tprintf(_T("%s reversed is '%s'\n"),str,_tcsrev(_tcsdup(str)));,意思就是对str的一份拷贝做reverse操作。这样一来,结果为:
Astring reversed is 'gnirtsA'
请按任意键继续. . .
既然_tcsdup(str)已经获得了一份str的拷贝,那么_tcsrev()就不会再对_TCHAR*造成access violation越界访问了。因此,如果使用了_tcsdup,大可以仍然保留_TCHAR* str=_T("Astring")这样的写法,而无需修改为str[]。
3.正确的msdn sample code运行起来怎么会报错?
在深入问题之前,需要了解一点儿故事。C是不支持const关键字的,因此它不得不容忍char* str="Astring"这样的用法。然而,"Astring"是const char*类型的,它是一个文字常量,不可更改。因此,在C++中,理应取消对char* str="Astring"语句的支持,甚至应该大胆的报错,告诉程序员这样的用法不对。但是,VS2010在编译时并没有报错,编译成功了,只是在运行时它提示了访问越界。
那么,也就是说,在早期的C编译器里,上面的代码编译运行应该不会有错。安装Turbo C++ 3.0,使用如下的代码:
/*++ cstr.c * *version:1.3 *created:2011-08-14 21:30 * * compiled and executed in Turbo C++ 3.0 --*/ #include <stdio.h> #include <stdlib.h> #include <string.h> void main() { char* str="Astring"; printf("%s reserved is ",str); printf("'%s'\n",strrev(str)); system("pause"); }
运行结果为:
由此可见,早期的编译器的确允许char* str="Astring"的用法。
这完全是出于向后兼容的考虑,C++问世之际,大量C程序采用了这种用法,如果要求全部修改势必造成很大麻烦。因此早些时候,明明不正确的语法却仍然被编译器所支持。我本想使用VC 6.0进行这个测试,但由于Win7的兼容性问题,VC 6.0跑不起来。如果哪位朋友有这个兴趣,不妨用VC 6.0试试看。如果它也允许这样的语法,并且运行起来没报错,那就真正能够说明编译器对这个问题的态度转变了。