飘雪

C++博客 首页 新随笔 联系 聚合 管理
  31 Posts :: 0 Stories :: 60 Comments :: 0 Trackbacks
    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版本上就不会再有问题,但是可能其中仍有隐患,等到具体碰到的时候再说吧。先把目前的工作继续下去。

posted on 2009-03-12 14:04 飘雪 阅读(4588) 评论(5)  编辑 收藏 引用

Feedback

# re: vc 2005 sp1下isspace函数对中文处理有问题[未登录] 2009-03-13 17:39 foxriver
自己写一个isspace啊,十几行的事情,我一直不太相信vc crt string部分。  回复  更多评论
  

# re: vc 2005 sp1下isspace函数对中文处理有问题 2009-03-13 17:54 飘雪
@foxriver
自己写一个isspace啊,十几行的事情,我一直不太相信vc crt string部分

最终他给我的是一个静态库,在静态库里调用的isspace,没法自己写呀,只能绕过去  回复  更多评论
  

# re: vc 2005 sp1下isspace函数对中文处理有问题 2009-04-05 21:13 Dancefire
如果我没有理解错,你试图用locale为ASCII的isspace来判断GBK编码的空格,对么?如果我理解正确的话,那么这不是VC的问题,而是使用上的问题。

对于C++而言,应该使用isspace(ch, loc); 这个版本,loc是类型为std::locale的变量,如果你想判断GBK的空格,那么让loc是GBK的locale,然后这个函数就正常了。

你现在使用的是C的isspace(ch)函数,这个函数使用的是默认的全局locale,你把这个全局的设为GBK,也应该可以解决这个问题。总之调用locale为默认的ASCII的locale的isspace去判断编码为GBK的字串是否是空格,逻辑上不对。  回复  更多评论
  

# re: vc 2005 sp1下isspace函数对中文处理有问题 2010-10-27 10:07 leeeryan
@Dancefire
同意楼上,分析的很在理  回复  更多评论
  

# re: vc 2005 sp1下isspace函数对中文处理有问题[未登录] 2012-12-17 13:59 jack
我也遇到这个问题,输出“包”字符(0xfc)时,用isspace检查返回值为0x08,所以输出乱码。后来我使用iswspace就好了,程序中应该尽可能的使用宽字符函数  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理