(点击上方公众号,可快速关注)
前几天在运行一段代码的时候,发现localtime
执行失败,由于代码没有判断返回值,后续对空指针操作导致段错误。所以,需要对该段代码增加保护判断,避免程序崩溃。由于后面的代码需要拷贝返回的struct tm
,所以理所当然地采用了localtime_s
改写,噩梦由此开始。。。
代码如下,我们会在localtime_s
执行失败的情况下返回false
。
// Windows下的源码
struct tm tmp = { 0 };
time_t t = xxx;
if(!localtime_s(&tmp, &t))
{
return false;
}
这类带后缀_s
统称安全函数,是微软发明创造,并推荐加入C标准库的。讽刺的是,VC++也是微软家的,但竟然跟标准差了那么多。
第一宗罪
VC++的localtime_s
参数顺序跟标准不一致。
VC++版的函数签名:
errno_t localtime_s( struct tm * result, const time_t * time);
标准版的为:
struct tm *localtime_s(
const time_t *restrict time,
struct tm *restrict result);
顺序正好相反。但好在两个参数的类型不一致,C/C++的类型系统是可以检出这个错误的。
第二宗罪
VC++的localtime_s
返回类型跟标准不一致。
VC++版返回errno_t
,实际上是int
的别名,函数执行成功时,会返回0,否则返回非0,所以判断失败的代码为:
if (localtime_s(...,...))
{
//失败
}
而,标准版返回struct tm *
,成功时为有效的struct tm
指针,失败时返回null指针,对应的失败判断语句:
if (!localtime_s(...,...))
{
//失败
}
两者的判断逻辑正好相反,这是大坑啊,而且编译器也帮不了忙。所以文章一开始的代码在VC++下是有问题的。
实际上,我躲过了第一个坑,栽在了这个坑里头。
第三宗罪
Windows SDK不同版本localtime_s
行为也有差异。
以下代码在VS 2012里(Windows SDK 8.1)执行没问题,在VS2019(Windows SDK 10,准确点是10.0.19041.0)执行失败。
struct tm tmp = { 0 };
time_t t = INT_MAX;
localtime_s(&tmp, &t);
特别说明:这个行为的差异具体是哪个版本引入的、以及临界值为多少,我没有进一步测试。
教训总结
不要太相信标准,存在”特立独行“的一些例外。
这里仅捉到了
localtime_s
,其他带_s
的函数有没有类似的问题,我也没进一步挖掘,大家遇到这类函数一定要小心。_s
后缀函数虽然加入了标准库的扩展,还没得到GCC、Clang等编译器的支持。所以能不用就不用了,这样还能降低入坑的可能;可以用
localtime
代替localtime_s
,虽然”不安全“,但基本上所有的编译器都支持。
喜欢我的文章,请关注我的公众号。转载请标明出处。