魔都进入夏季,多雨闷热,坐着不动也可能汗流浃背。最近忙于项目和书稿,有点顾不上写公众号了。今日周末,忙里偷闲,把昨日遇到的一个真实问题与大家分享一下。

故事的开端是在使用一个新的样本(视频文件)测试我的识别程序时,VS报告了下面所示的运行期检查错误。

setlocaleutf8_setlocaleapi_setlocale

点击重试进行调试,观察栈回溯,发生在Windows 10引入的ucrt库中。

setlocale_setlocaleutf8_setlocaleapi

默认没有加载符号,所以只显示了UCRT的函数的地址,触发加载符号。

setlocaleutf8_setlocaleapi_setlocale

有了符号后,便可以看到完整的执行经过了。

setlocaleapi_setlocaleutf8_setlocale

可以看出,我的程序调用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.

对于调试版本,发起断言。

其实,也可能触发非法访问,把程序搞翻!

setlocaleapi_setlocale_setlocaleutf8

刚遇到这个问题时,我还以为是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

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注