代理偏见

举报
飞得乐 发表于 2024/07/26 11:25:29 2024/07/26
【摘要】 近期在某运行环境上看到一条提示信息,说出现运行错误时,可能是由于调用了非标准库函数引起的。我对此非常疑惑:“非标准库函数”这个概念太大了,除了编程语言自带的标准库以外,任何一个函数都是非标准库函数——Linux操作系统API也是,第三方开源库函数也是,同一个项目里的其他模块函数也是,甚至我自己代码里定义一个函数也是“非标准库函数”。调用非标准库函数会不会导致运行错误?当然有可能,调用任何一个...

近期在某运行环境上看到一条提示信息,说出现运行错误时,可能是由于调用了非标准库函数引起的。

我对此非常疑惑:“非标准库函数”这个概念太大了,除了编程语言自带的标准库以外,任何一个函数都是非标准库函数——Linux操作系统API也是,第三方开源库函数也是,同一个项目里的其他模块函数也是,甚至我自己代码里定义一个函数也是“非标准库函数”。

调用非标准库函数会不会导致运行错误?当然有可能,调用任何一个函数都可能导致运行错误——即使是标准库。C语言标准库中有一大堆接收内存地址参数的函数,随便挑一个传个错误地址进去,想跑出错误轻而易举。但问题是,运行错误和是不是“非标准库函数”这个特征有一丁点儿联系吗?如果没有联系,为什么要强调这个特征呢?

我向人询问这个提示的来源,得到了这么一个故事:

几年前,很多人在这个环境上写C代码时,习惯性的使用strdup这个函数,发现只要用了strdup就会产生程序运行时的signal错误,大家觉得是strdup函数有问题。由于编译C代码时指定了C11标准,而strdup不属于C11标准库函数,于是就写出了这条提示让大家不要调用非标准库函数。

我好奇strdup为什么会出问题,就去环境上试了几下,再在网上一搜,得知是这么个原因:

https://stackoverflow.com/questions/8359966/strdup-returning-address-out-of-bounds

当指定-std=c11参数时,gcc认为用户想要尽可能遵循C11标准,就在string.h头文件中屏蔽了strdup函数声明。这时用户写的代码中调用的strdup是个没有被声明的函数。C语言允许调用未声明的函数,但会自动把函数返回值声明为int类型。于是,本来返回char*类型的strdup函数,在编译时被当做返回int类型。运行时strdup返回的指针被转换为int导致值被截断,后续访问这个指针产生未定义行为。

这个问题的本质原因来自C语言的陷阱——调用未声明的函数也能编译通过,而且函数返回值自动声明为int。别说strdup,任何一个返回指针的函数未声明就调用都会出一样的问题。对于有经验的C程序员来说,只要关注一下编译告警就能发现这种问题。

gcc在指定-std=c11参数时,屏蔽掉strdup声明这个做法对不对呢?——在Linux中,string.h头文件包含有strdup,但是在C11语言标准中,string.h头文件又不包含strdup,这使得编译器必须做出选择。在默认配置下,gcc选择包含strdup的声明,但如果这时用户代码中自定义了strdup,会和已有定义产生冲突。然而严格按C11标准,这种自定义strdup的代码是应该正常编译运行的。所以,为了遵从C11标准,gcc选择了在指定-std=c11参数时不包含strdup的声明(但也可以通过定义特定的宏来打开)。

再回头看,开始时那条提示信息的产生过程是这样的:

代码运行出错了 -> 定位发现只要用了strdup就出错 -> 运行环境上strdup有问题(第一次“代理”) -> 非标准库函数有风险(第二次“代理”) -> 写个提示不要用非标准库函数

上面的流程中,两次不当的代理导致了最后这个奇怪的结论。第一次代理:“特定编译环境下用了strdup的代码出问题”代理为“strdup函数有问题”;第二次代理:"strdup函数有问题”代理为“非标准库函数有风险”。

我把这种不当的代理叫做“代理偏见”。代理偏见在各种讨论中广泛的存在,最著名的是网上的“地图炮”:“新闻中老有某个省的人偷井盖”——代理为“某个省的人全都偷井盖”。

代理偏见能这么流行,我估计一个重要的原因是它省脑容量。通常一个真实的因果关系要包含很多成立的前提,但这么多细节既不利于记忆也不利于传播,而代理为一个简单粗暴的结论,脑容量就省下很多了。“在使用了-std=c11编译参数时,gcc屏蔽string.h中strdup函数声明导致用户代码中strdup被声明为返回int使得指针被截断”这多难记啊,代理一下,变成“使用非标准库函数会导致执行出错”这就好记多了。至于对不对,那先不管,抛开事实不谈,至少后面那个结论它好记好传播。

有意思的是,现在大量的人表面上似乎对于代理偏见很警惕,知道它不对,有时还能指出这种问题,网络论坛也都在强调不要地图炮。但同时,在和很多人讨论问题的时候又经常能见到代理偏见大行其道。

说几个我经常听到的结论吧,大家看是不是很常见:

圈复杂度低的代码是cleancode,圈复杂度高的代码不是好代码;
用了C安全函数的代码安全,用了非安全函数的代码不安全;
C++代码中函数传参时传引用比传值性能高;
前置自增操作符比后置自增操作符的性能高;
同样功能,用多线程实现比用单线程实现性能高;
在单核CPU上运行时,并发读写基本数据类型不用加锁;
……

如果以上这些结论中有任何一个是你深信不疑的,恭喜你,你就是代理偏见大军中的一员。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。