vc 2005 sp1下isspace函数的debug版本对中文处理有问题
今天碰到一个怪问题,从别人那儿拿来的一段代码先在gcc下过了,又移植到vc下编译,结果debug时老是有assert错误。看了一下代码,错误发生在一个trim函数中。trim函数接受一个char*类型的字符串参数,去掉字符串前后的空格、制表符等空白字符。其中判断是否是空白字符用的是isspace函数。按照一般的想法,char*字符串里的字符编码无论是GBK还是utf-8,因为都兼容ASCII,所以isspace函数都不应该发生问题。但事实是只要是字符串有中文,无论是gbk还是utf-8编码,isspace内都有assert错误。为了便于说明,把其中的代码抽像出来如下:
char* lpszBuffer = (char*)"高";
int nLen = (int)strlen(lpszBuffer);
for( int i=0;i
{
printf("0x%x %dn",lpszBuffer[i],isspace(lpszBuffer[i]) );
}
这段代码在debug编译的情况下会assert失败。没有办法,只好跟踪到c运行库里,isspace的实现如下(在"_ctype.c"文件里):
extern __inline int (__cdecl isspace) (
int c
)
{
if (__locale_changed == 0)
{
return __fast_ch_check(c, _SPACE);
}
else
{
return (_isspace_l)(c, NULL);
}
}
跟踪发现,__locale_changed的值为0,走第一个分支,调用__fast_ch_check,它其实是个宏定义,最后进入_chvalidator函数,它的实现代码如下:
extern "C" int __cdecl _chvalidator(
int c,
int mask
)
{
_ASSERTE((unsigned)(c + 1) <= 256);
return _chvalidator_l(NULL, c, mask);
}
错误就发生在这个函数的第一行“ _ASSERTE((unsigned)(c + 1) <= 256);”。
代码看到这里,错误的原因基本也就出来了。“高”的GBK编码是“b8 df”,调用isspace函数是逐个字节判断,但是isspace和_chvalidator接受的参数都是int,这样就会产生一个char到int的转型,在vc下,char默认是"signed char",这样char“b8”转型到int后,会变成一个负数,然后在assert的时候,又强制转型为unsigned,它又变成了一个非常巨大的正数,自然assert就失败了。
为什么在gcc下没有错误?gcc下的isspace函数实现我倒没看,不过我的gcc中的char默认就是“unsighed char”,所以即使isspace的实现跟上面的一样,也不会产生问题。
另:在win32 release和wince下,也不会有上述问题,因为这几种环境下isspace的实现跟上面不一样。
问题找到了,剩下的就看怎么解决了。
首先用用一个最简单的方法,既然问题发生在转型上,只要把char的默认类型改为unsigned char就可以了,vc也提供了这个选项。但这有几个问题,一是别人用我的代码的时候还得修改编译选项,二是修改了整个工程的编译选项可能会对其它代码产生不好的影响。
既然上面的方法不大好操作,那就再想想别的方法。经观察发现,isspace函数中靠__locale_changed变量控制流程走向,搜索整个crt的源代码,发现__locale_changed的值只有在setlocale函数中发生了改变。最后,我把代码修改成如下:
#if defined(WIN32) && defined(_DEBUG)
char* locale = setlocale( LC_ALL, ".OCP" );
#endif
char* lpszBuffer = (char*)"高";
int nLen = (int)strlen(lpszBuffer);
for( int i=0;i
{
printf("0x%x %dn",lpszBuffer[i],isspace(lpszBuffer[i]) );
}
这样在debug版本上就不会再有问题,但是可能其中仍有隐患,等到具体碰到的时候再说吧。先把目前的工作继续下去。