isspace函数的debug版本处理中文字符时程序异常

本文深入探讨了GCC和VC++编译器下isspace函数的行为差异,特别是在处理GBK编码字符串时出现的assert失败问题。通过代码示例和详细分析,揭示了问题根源在于char类型的不同默认设置,以及如何通过修改编译选项或使用setlocale函数来解决这一跨平台兼容性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

示例代码:

    #include <iostream>
    #include <string>
    #include <fstream>
    #include <algorithm>
    #include <functional>
    #include <locale>
    #include <cctype>
     
    using namespace std;
     
    inline string& ltrim(string &str) {  
        string::iterator p = find_if(str.begin(), str.end(), std::not1(ptr_fun<int, int>(isspace)));  
        str.erase(str.begin(), p);  
        return str;  
    }  
     
    inline string& rtrim(string &str) {  
        string::reverse_iterator p = find_if(str.rbegin(), str.rend(), std::not1(ptr_fun<int , int>(isspace)));  
        str.erase(p.base(), str.end());  
        return str;  
    }  
     
    inline string& trim(string &str) {  
        ltrim(rtrim(str));  
        return str;  
    }  
     
    int main(){
     
        #if _DEBUG
        setlocale(LC_ALL, "chs");
        #endif
        string str = "\t\r\n 123sggdery中 国455 68 \r\n";  
        string str1 = str;  
        string str2 = str;  
     
        cout << "str: ~" << str << "~" << endl << endl;  
     
        cout << "ltrim(str): ~" << ltrim(str1) << "~" << endl;  
        cout << "rtrim(ltrim(str)): ~" << rtrim(str1) << "~" << endl << endl;  
     
        cout << "rtrim(str): ~" << rtrim(str2) << "~" << endl;  
        cout << "ltrim(rtrim(str)): ~" << ltrim(str2) << "~" << endl << endl;  
     
        cout << "trim(str): ~" << trim(str) << "~" << endl;  
     
        return 0;  
    }


上述 代码(没有设置 setlocale 的时候 )在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函数中发生了改变。最后,我把代码进行修改 添加了   setlocale 解决此问题
---------------------

原文:https://blog.youkuaiyun.com/liangzhao_jay/article/details/78064399

#ifndef _HASHSTRING_H_ #define _HASHSTRING_H_ #include <tuple> namespace INISTRING { typedef unsigned int uint32_t; class HashString { public: HashString() = delete;//默认构造函数, 禁止默认构造 HashString(const char* _szString);//带参构造函数 HashString(const HashString& _Instance)=delete;//删除复制构造函数, 无法复制 HashString(HashString&& _Instance)noexcept;//移动构造函数, std::move 触发 ~HashString();//析构函数 HashString& operator=(const HashString& _Instance)=delete;//删除重载赋值函数,无法赋值 HashString& operator=(HashString&& _Instance)noexcept;//重载赋值移动函数: std::move触发 bool operator==(const HashString& _Instance)const;//重载等于符号 bool operator>(const HashString& _Instance)const;//重载大于符号 bool operator<(const HashString& _Instance)const;//重载小于符号 HashString& operator=(const char* _szText);//重载赋值函数 const char* GetString()const;//获取字串首地址 const uint32_t GetLength()const;//获取字串长度 bool IsEmpty()const;//判断字串是否空字串 const unsigned long long Convert()const; private: //<const char* szHead, uint32_t m_iLength, uint32_t m_iHash1, uint32_t m_iHash1a> std::tuple<const char*, uint32_t, uint32_t, uint32_t> CalcString(const char* _szText); uint32_t FNVHash1(const char* _szText, uint32_t _iLength); uint32_t FNVHash1a(const char* _szText, uint32_t _iLength); private: char *m_szText; uint32_t m_iLength; //采用双重hash验证, hash双重算法使用FNV-1和FNV-1a struct HashValue { uint32_t m_iHash1a; uint32_t m_iHash1; }; HashValue m_vecHash; unsigned long long* m_pHash;//指向m_vecHash内存块的指针,用来快速比较 }; } #include "HashString-inl.h" #endif#include "HashString.h" #ifdef _DEBUG //#define _INISTRING_DEBUG_ #endif // DEBUG #ifdef _INISTRING_DEBUG_ #include "../../DebugInfo/Include/DebugInfo.h" #endif // _INISTRING_DEBUG_ inline bool IsSpace(const unsigned char* _pByte); /* { return\ *_pByte == ' ' || *_pByte == '\t' || *_pByte == '\n' || *_pByte == '\v' || *_pByte == '\f' || *_pByte == '\r'; }*/ /* # FNV - 1算法公式 hash = FNV_offset_basis for each byte_of_data to be hashed hash = hash * FNV_prime hash = hash ^ byte_of_data return hash # FNV - 1a算法公式 hash = FNV_offset_basis for each byte_of_data to be hashed hash = hash ^ byte_of_data hash = hash * FNV_prime return hash */ inline uint32_t CalcHash1(const unsigned char _Byte, uint32_t _iOffset, const uint32_t _iFNVPrime = 16777619) { _iOffset *= _iFNVPrime; _iOffset ^= _Byte; return _iOffset; } inline uint32_t CalcHash1a(const unsigned char _Byte, uint32_t _iOffset, const uint32_t _iFNVPrime = 16777619) { _iOffset ^= _Byte; _iOffset *= _iFNVPrime; return _iOffset; } uint32_t INISTRING::HashString::FNVHash1(const char* _szText, uint32_t _iLength) { uint32_t iHash = 2166136261;//初始化hash 32位的填充值 uint32_t iFNVPrime = 16777619;//FNV的32位的散列因子? for (unsigned int i = 0; i < _iLength; ++i) { iHash *= iFNVPrime; iHash ^= _szText[i]; } return iHash; } uint32_t INISTRING::HashString::FNVHash1a(const char* _szText, uint32_t _iLength) { uint32_t iHash = 2166136261;//初始化hash 32位的填充值 uint32_t iFNVPrime = 16777619;//FNV的32位的散列因子? for (unsigned int i = 0; i < _iLength; ++i) { iHash ^= _szText[i]; iHash *= iFNVPrime; } return iHash; } std::tuple<const char*, uint32_t, uint32_t, uint32_t> INISTRING::HashString::CalcString(const char* _szText) { //放弃优化,因为key的长度一般很短。优化复杂度如下: //1. 过滤字串头部和尾部的空格 //2. 计算字串的长度(已经过滤头尾空格) //3. 计算字串的hash值 //如果要优化,复杂度代价可能比直接while逐个解析char的代价还要大 //register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中 //而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。 //毕竟cpu寄存器有限 //解析过程: 每次抽4byte逐个解析, 因为提供的字串流可能是存在于硬盘。 //存在于硬盘, 每次抽4byte到内存 比起 每次抽1byte到内存 可能更快 const char* szHead = nullptr;//过滤空格后的字串头地址,用作返回值 const char* szTail = nullptr;//过滤空格后的字串尾地址 const char* szSource = _szText;//目标字串操作指针 register uint32_t iMagicBits = 0x7EFEFEFFL;//cpu进行位运算所需的因子数,计算4byte里面是否含有空字符'0' register uint32_t iBlock;//抽出来的4byte配合MagicBits进行位运算,快速判断4byte里面是否含有空字符'0' unsigned char vecBuffer[sizeof(uint32_t)];//抽出4byte的储存容器 //计算hash的思路,采用极端情况的字串: " string text " ,此字串头尾和中间都有空格。并且需要过滤掉头尾空白字符的同计算出hash值 //1.开始计算hash肯定跳过字串头部的空格 //2.字串中间的空格只能当作临hash计算,因为你不知道这个空格后面会不会都只有空格(此空格后面可能还有有效字符)。此计算空格后的hash存放在iTmpHash内 //3.iHash所存放的都是最一个非空格字符的hash,这样即使在字串中间遇到空格字符,也能照常向下计算hash(计算所得放在iTmpHash),因为如果之后的字符全是空格的话,此iHash就是最终的hash了,而iTmpHash就永远被放弃了 //处理hash值使用的数据, FNV1版本 uint32_t iHash1 = 0;//储存最终hash值的容器 uint32_t iTmpHash1 = 2166136261;//储存临hash值的容器,并填充初始值 //处理hash值使用的数据, FNV1a版本 uint32_t iHash1a = 0;//储存最终hash值的容器 uint32_t iTmpHash1a = 2166136261;//储存临hash值的容器,并填充初始值 while (true) { *(uint32_t*)vecBuffer = *(uint32_t*)szSource;//从字串流中抽取4byte到容器 iBlock = *(uint32_t*)vecBuffer;//把抽出来的4byte将由cpu计算 if ((((iBlock + iMagicBits) ^ ~iBlock) & ~iMagicBits) != 0)//计算4byte里面是否含有空字符'0' {//含有空字符'0' for (int i = 0; i < sizeof(uint32_t); ++i) { //找到字串末尾,打包并返回所需数据 if (vecBuffer[i] == '\0') { return std::make_tuple(szHead, szTail - szHead, iHash1, iHash1a); } if (IsSpace(vecBuffer + i))//如果是空白字符 { //字串头地址已经找到,此空白字符开始计算hash值。并储存在临hash容器 if (szHead) { iTmpHash1 = CalcHash1(vecBuffer[i], iTmpHash1);//FNV1版本 iTmpHash1a = CalcHash1a(vecBuffer[i], iTmpHash1a);//FNV1a版本 } //else 字串头都未找到,说明正在过滤字串头部空格 } else//如果不是空白字符 { if (!szHead)szHead = szSource + i;//找到字串头地址 szTail = (szSource + i) + 1;//计算字串尾地址 //FNV1版本 iTmpHash1 = CalcHash1(vecBuffer[i], iTmpHash1);//计算hash iHash1 = iTmpHash1;//此字符非空白字符,临hash值转交给最终hash值 //FNV1a版本 iTmpHash1a = CalcHash1a(vecBuffer[i], iTmpHash1a);//计算hash iHash1a = iTmpHash1a;//此字符非空白字符,临hash值转交给最终hash值 } } } else {//不含空字符 for (int i = 0; i < sizeof(uint32_t); ++i) { if (IsSpace(vecBuffer + i))//如果是空白字符 { //字串头地址已经找到,此空白字符开始计算hash值。并储存在临hash容器 if (szHead) { iTmpHash1 = CalcHash1(vecBuffer[i], iTmpHash1);//FNV1版本 iTmpHash1a = CalcHash1a(vecBuffer[i], iTmpHash1a);//FNV1a版本 } //else 字串头都未找到,说明正在过滤字串头部空格 } else//如果不是空白字符 { if (!szHead)szHead = szSource + i;//找到字串头地址 szTail = (szSource + i) + 1;//计算字串尾地址 //FNV1版本 iTmpHash1 = CalcHash1(vecBuffer[i], iTmpHash1);//计算hash iHash1 = iTmpHash1;//此字符非空白字符,临hash值转交给最终hash值 //FNV1a版本 iTmpHash1a = CalcHash1a(vecBuffer[i], iTmpHash1a);//计算hash iHash1a = iTmpHash1a;//此字符非空白字符,临hash值转交给最终hash值 } } } szSource += sizeof(uint32_t); } } /* //默认构造函数 INISTRING::HashString::HashString():m_szText(nullptr),m_iLength(0),m_vecHash{0}, m_pHash((long long*)(&m_vecHash)) { #ifdef _INISTRING_DEBUG_ Info("默认构造: 创建新HashString空白对象<{}>", fmt::ptr(this)); #endif // _INISTRING_DEBUG_ } */ //带参<char*>构造函数 INISTRING::HashString::HashString(const char* _szText) : m_szText(nullptr), m_iLength(0),m_vecHash{0}, m_pHash((unsigned long long*)(&m_vecHash)) { #ifdef _INISTRING_DEBUG_ Info("带参构造<char*>: 创建新HashString对象<{}>", fmt::ptr(this)); if (!_szText)Error("带参构造<char*>:不能使用空指针初始化HashString:{}", fmt::ptr(this)); assert(_szText != 0 && "不能使用空指针初始化HashString"); #endif // _INISTRING_DEBUG_ const char* szHead = nullptr;//过滤空格后的字串头地址,用作返回值 std::tie(szHead, m_iLength, m_vecHash.m_iHash1, m_vecHash.m_iHash1a) = CalcString(_szText);//计算字串,并解包返回值 //uint32_t iHash1 = FNVHash1(szHead, m_iLength);//调试对比 //uint32_t iHash1a = FNVHash1a(szHead, m_iLength);//调试对比 if (m_iLength) { uint32_t iLength = (sizeof(uint32_t) - (m_iLength % sizeof(uint32_t))) + m_iLength;//求对齐数 m_szText = new char[iLength];//创建内存对齐的内存块 memcpy(m_szText, szHead, m_iLength);//复制字串到内存块 m_szText[m_iLength] = '\0';//强制帮字串末尾加入空字符 #ifdef _INISTRING_DEBUG_ Info("字串内存块地址:{},内容<{:s}>", fmt::ptr(m_szText), m_szText); #endif } #ifdef _INISTRING_DEBUG_ else Warn("带参构造<char*>: 创建新HashString对象<{}>, 目标是空字串", fmt::ptr(this)); #endif // _INISTRING_DEBUG_ } //移动构造函数, std::move 触发 INISTRING::HashString::HashString(HashString&& _Instance)noexcept: m_szText(nullptr), m_iLength(0), m_vecHash{ 0 }, m_pHash((unsigned long long*)(&m_vecHash)) { #ifdef _INISTRING_DEBUG_ Info("HashString对象<{}>触发std::move, move对象<{}>", fmt::ptr(this), fmt::ptr(&_Instance)); #endif // _INISTRING_DEBUG_ *this = std::forward<HashString>(_Instance);//完美转发右值引用 } //重载赋值移动函数: std::move触发 INISTRING::HashString& INISTRING::HashString::operator=(HashString&& _Instance)noexcept { if (m_szText) { #ifdef _INISTRING_DEBUG_ Info("HashString对象<{}>移动赋值, 正在销毁字串内存块<{}>", fmt::ptr(this), fmt::ptr(m_szText)); #endif // _INISTRING_DEBUG_ delete m_szText; } m_szText = _Instance.m_szText; m_iLength = _Instance.m_iLength; m_vecHash = _Instance.m_vecHash; _Instance.m_szText = nullptr; _Instance.m_iLength = 0; _Instance.m_vecHash.m_iHash1 = 0; _Instance.m_vecHash.m_iHash1a = 0; return *this; } //析构函数 INISTRING::HashString::~HashString() { #ifdef _INISTRING_DEBUG_ Info("析构HashString对象,对象地址:{}, 正在销毁字串内存块<{}>", fmt::ptr(this), fmt::ptr(m_szText)); #endif // _INISTRING_DEBUG_ if (m_szText)delete m_szText; } //重载比较 等于函数 bool INISTRING::HashString::operator==(const HashString& _Instance)const { return *m_pHash == *_Instance.m_pHash; } //重载比较 大于函数 bool INISTRING::HashString::operator>(const HashString& _Instance)const { return *m_pHash > *_Instance.m_pHash; } //重载比较 小于函数 bool INISTRING::HashString::operator<(const HashString& _Instance)const { return *m_pHash < *_Instance.m_pHash; } //重载赋值函数 INISTRING::HashString& INISTRING::HashString::operator=(const char* _szText) { #ifdef _INISTRING_DEBUG_ Info("HashString对象<{}>触发重载赋值函数", fmt::ptr(this)); #endif // _INISTRING_DEBUG_ * this = HashString(_szText); return *this; } //获取字串首地址 const char* INISTRING::HashString::GetString()const { return m_szText; } //获取字串长度 const uint32_t INISTRING::HashString::GetLength()const { return m_iLength; } //判断字串是否空字串 bool INISTRING::HashString::IsEmpty()const { return m_iLength ? true : false; } const unsigned long long INISTRING::HashString::Convert()const { return *m_pHash; } 根据我提供的代码,帮我优先它,但不要更改类名
最新发布
07-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值