魔都进入夏季,多雨闷热,坐着不动也可能汗流浃背。最近忙于项目和书稿,有点顾不上写公众号了。今日周末,忙里偷闲,把昨日遇到的一个真实问题与大家分享一下。
故事的开端是在使用一个新的样本(视频文件)测试我的识别程序时,VS报告了下面所示的运行期检查错误。
点击重试进行调试,观察栈回溯,发生在Windows 10引入的ucrt库中。
默认没有加载符号,所以只显示了UCRT的函数的地址,触发加载符号。
有了符号后,便可以看到完整的执行经过了。
可以看出,我的程序调用opencv,后者调用了ucrt。重点关注上面的部分,摘录如下:
ucrtbased.dll!_chvalidator(int c, int mask) 行 36C++
>ucrtbased.dll!fast_check(const int c, const int mask) 行 24C++
ucrtbased.dll!isdigit(int c) 行 79C++
opencv_world400d.dll!cv::extractPattern(const std::basic_string,std::allocator > & filename, unsigned int & offset) 行 241C++
看来是opencv的extractPattern函数调用了C函数isdigit。
主角出场了,我们标题中说的简单函数指的就是isdigit,它是C语言中的标准库函数,功能就是判断参数指定的字符是否是数字。
int isdigit( int c );
”这么简单“的函数怎么会出错呢?
长话短说,ucrt把它实现的挺复杂的,原因是为了支持多语言。如果只处理英文和ASCII码,那是太简单了, 只要判断一下参数c是否是0-9这10个字符的编码,真是一两行代码就搞定了。
但是不知道是哪位天才提出的要求,或许是有某个天才的产品经理提出来的吧?说要支持多语言,希望输入的字符c是宽字符表示的数字时也返回真。
iswdigitreturns a nonzero value ifcis a wide character that corresponds to a decimal-digit character.
【来自微软网站isdigit函数的官方文档】
举例来说,想想下面这个字符串,问题可不复杂了。
_T(“1234汉语一壹全角12”);
于是乎,原本简简单单的一个函数,与臃肿、复杂、混乱的locale机制扯到一起了。
回到我们上面的问题,就是在考虑了locale的情况下调用isdigit触发地雷了。
触雷的导火索是一个中文文件名,我新拿到一批样本文件,都是中文命名的。
上面的断言是在抱怨参数字符不在规定的范围内。规定的范围是-1到255,触发错误的中文字符的整数表达是-77。
长话短说,ucrt中的isdigit实现的足够复杂,可能有预想不到的结果,也可能发起异常。
对此,微软官方文档是有明文警告的:
Thebehavior ofisdigitand_isdigit_lisundefinedifcis not EOF or in the range 0 through 0xFF,inclusive.
如果c不在规定范围内,那么其行为是未定义的!
Whena debug CRT library is used andcis not one of these values, the functions raisean assertion.
对于调试版本,发起断言。
其实,也可能触发非法访问,把程序搞翻!
刚遇到这个问题时,我还以为是OpenCV的问题,因为我是调用OpenCV的函数打开视频文件,于是顺口说:”难道OpenCV不支持中文文件名么?“,一旁的老朋友立刻表示反对:”怎么会?“
那么现在看来,OpenCV的代码是有些责任的,不管文件名里拿到什么字符都调用isdigit。
那么如何化解这样的问题呢?官方的建议是要设置locale。以上面提到过的这个字符串为例:
_T(“1234汉语一壹全角12”);
如果使用默认的C locale,那么调试版本会有异常,发布版本返回的结果匪夷所思。
Results are TTTTFTFTFFTF for C
前四个没问题,但是”语“字居然返回真!
如果设置了中文locale。
setlocale(LC_ALL,”zh-CN”);
那么调试版本也不再有异常,返回的结果也好理解一些了:
Results are TTTTFFFFFFFF for zh-CN
如果设置为英文locale,那么调试版本会出异常,发布版本,会非法访问,UCRT被激怒了,哈哈哈。
如果格友看了后,想试一下,那么核心代码如下:
#include “pch.h”
#include
#ifdef _WIN32
#include
#else
#define TCHAR char
#define _T(s) s
#define _tcslen(s) strlen(s)
#include
#endif
void DCheck(TCHAR * szText)
bool bDigit;
int i;
char szIsDigit[20];
for (i = 0; i < _tcslen(szText); i++)
bDigit = isdigit(szText[i]);
szIsDigit[i] = bDigit ? ‘T’ : ‘F’;
szIsDigit[i] = 0;
printf(“Results are %s for %sn”,szIsDigit, setlocale(LC_ALL, NULL));
int main()
TCHAR szText[] = _T(“1234汉语一壹全角12”);
DCheck(szText);
setlocale(LC_ALL, “zh-CN”);
DCheck(szText);
setlocale(LC_ALL, “en-US”);
DCheck(szText);
再附送一个截图。
如果有人问,LINUX下会如何呢?简单还是好,问题少。
fireeye@fireeye:~/LocDigit$ ./dg
Results are TTTTFFFFFFFFFFFFFFFFFFFFFFFF for C
Results are TTTTFFFFFFFFFFFFFFFFFFFFFFFF for C
Results are TTTTFFFFFFFFFFFFFFFFFFFFFFFF for C
不支持设置locale,始终用C locale,判断的结果也是合乎程序员需要的。
***********************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
欢迎访问了解软件调试高级研习班的最新信息
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777