编译器对const char*的态度转变

 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试试看。如果它也允许这样的语法,并且运行起来没报错,那就真正能够说明编译器对这个问题的态度转变了。

你可能感兴趣的:(编译器对const char*的态度转变)