专栏首页裸机思维【编译器玄学研究报告】第五期——三十年老娘倒绷孩儿

【编译器玄学研究报告】第五期——三十年老娘倒绷孩儿

【说在前面的话】


这是一件发生在我身上的真实事件,它根本不是一个故事——由于它差点就变成我人生的一次巨大乌龙——所以应该算是个事故。此前,我曾经发现过不下两位数的编译器Bug,所以一开始,当这一次事件到来的时候,我并未过多的产生过怀疑……

【诡异的错误信息】


那是个与往常无异的寂静夜晚,我像平常一样,关掉了小房间的灯,让黑暗如同浓厚的咖啡那样包裹着橘黄色台灯下的我。透过屏幕左上角的时钟,我注意到时间刚刚划过了子夜。作为一个独自在家办公已经一年多的老码农来说,此时才是一天中敲击代码最为惬意的时刻。

正当我抿了一口口味浓烈的82年矿泉水时,一个开源项目的微信窗口闪动了起来。群里的人熟识了太久,正因为无事不谈,搞得现在无事可谈——一个红色的“@”符号后赫然的写着我的名字——看来不是什么好兆头

“淦!”

我骂了一句,点开了聊天窗口。

你的模块在GCC下编译报错了”,雪白的窗口背后,此刻一定有一张不无嘲笑的嘴脸。

“怎么可能?” 我愤愤不平:“在clang和IAR下都测试过的代码怎么会在GCC中编译报错呢?

考虑到凡事不可把话说的太绝,我顿了顿补充道:“是不是你忘记打开-fms-extensions了?这是常见错误。”

“加了,因为这的确是你的代码出现编译故障的常见原因,所以我第一时间就处理了……呐!你看……”,聊天窗口里出现了截图,“我加了哦!”

“淦!”

我又骂了一句,由于想好的话被截图活生生压了回去——就好比哥斯拉铆足了力气、张大了嘴巴准备吐息时被人堵上了嘴——我一时不知道如何应对才好。

“编译报什么错误呢?”

当这句话从指尖流向屏幕时,我敢打包票,这完全是出于聊天的本能而不是本意——因为大脑此时正在飞速旋转,思考符合这一切的合理解释,换句话说,我对如下的事实其实完全丈二和尚摸不着头脑:

  • 同一段代码,在clang、IAR以及Arm Compiler 6下编译是没问题的,然而现象表明GCC报告了错误;
  • Clang以及Arm Compiler 6同根同源,它们都使用了GCC的语法前端,因此几乎可以这么断定:GCC里可以编译的代码,只要不涉及特殊的#pragma或者__attribute__在clang中几乎肯定是可以正确编译的;反之亦然。
  • 实践中经常会发现,clang比gcc的语法要严格,gcc很多时候在语法风格上更加“放飞自我”,因此clang中可以通过编译的代码,怎么会在GCC中无法编译通过呢?

“Bug!一定是编译器Bug!” 我几乎失去了理智一般脱口而出!同时这一想法马上又让子夜时分脑前叶近乎梦游的我觉得亢奋不已——难道我终于要在大佬云集的GCC界出人头地了?

此时,你一定非常好奇,究竟是怎样的代码让我如此笃定这是编译器Bug呢?尽管原本的应用代码结构看起来要复杂的多,但为了隔离问题,实际上最终能稳定复现问题的代码片断被简化的只剩下了一行:

#include <stdint.h>

static const uint32_t s_wTest = (0, 0x12345678);

对于我是如何使用逗号表达式产生如此骚操作而感到好奇的小伙伴,可以阅读这篇文章《【为宏正名】99%人都不知道的"##"里用法》。


这里的关键是右边的逗号表达式,关于它的用法很多人都并不陌生,简单说就是“依次执行逗号左边的表达式,并把最右边表达式的结果作为整个逗号表达式的返回值”。这里:

  • 无论是“0”还是“0x12345678”都是常数
  • 整个逗号表达式的结果怎么看在编译时刻都是确定的

究竟是谁给了GCC一个胆子在众目睽睽之下信口雌黄,扔出如下的错误信息?

reproducer.c:3:33: error: initializer element is not constant
    3 | static const uint32_t s_wTest = (0,0x12345678);
      |                                 ^

睁着眼睛说瞎话么?

你敢说这个表达式不是常量?!!!!

【交叉验证】


为了验证我的想法,我又在clang下做了同样的事情:

clang reproducer.c

得到了肯定的回答:

reproducer.c:3:34: warning: expression result unused [-Wunused-value]
static const uint32_t s_wTest = (0,0x12345678);
                                 ^
1 warning generated.

翻译下来,意思就是说,clang认为这个变量初始化是没问题的,只不过它发现你逗号表达式里有一个值其实没有真正被使用——没错,就是这个“0”——所以它产生了一个不痛不痒的warning:

  • 作为测试,这实际上告诉我们,clang是正常的认可了0x12345678作为逗号表达式的返回值
  • clang并没有认为这个表达式不是常量
  • clang也没有认为这个静态常量 s_wTest 的初始化有什么不妥;
  • 如果觉得这个warning不爽,我们大可以在命令行里加入 -Wno-unused-value来屏蔽它,比如:
clang reproducer.c -Wno-unused-value

实际上类似的测试我在Arm Compiler 6以及IAR中多做了测试,并没有遇到什么问题。我忍不住看了一眼GCC的版本:

gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)

好家伙,虽然不是最新的,但也还是乌班图之类Linux系统的当红靓仔啊!

难道我真的发现了一个如此明显的Bug?

【越想越不对……】


在做好了截图,写好了邮件准备全网放送的时候,为了十足的仪式感以及为日后写回忆录时可以有更多的谈资,我决定沐浴更衣后再焚香一柱以纪念这个历史性的时刻。

就在热水哗啦啦的冲刷着我3个月没有打理过过的一头乌黑靓丽的秀发时,我的内心逐渐从兴奋变为开心、从开心变为平顺、从平顺变得冷静——最后从冷静变成恐惧:

  • 这种语法前端的解析bug太明显了,不可能到了9.0版本还存在——实际上我在写邮件时试图追溯这个Bug最早从哪个版本引入的,尝试过5.0、6.x、10.x等多个版本——问题似乎一直都在那里;
  • 逗号表达式如此常见,很难想象我是第一个发现者
  • 难不成这是一个“feature”而不是“bug”?如果真的是这样,我岂不成了全网笑柄?

怀揣着这种恐惧,我草草的擦干了身子,头都来不及吹就急忙冲到了屏幕前,急不可耐的打开搜索引擎,开始寻找类似的问题。果不其然,在StackOverflow上看到了一模一样的问题:

链接如下:

https://stackoverflow.com/questions/1737634/c-comma-operator

好险,果然小丑就是我自己,而且我差点还要把它广而告之……

【小丑居然是我自己】


正如这个帖子所指出的,在ANSI-C99标准中,Section 6.6/3节对于常量表达式做出了专门的规定:

Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.

翻译一下就是:

常量表达式不应包含赋值、递增、递减、函数调用或逗号运算符……

问题似乎是水落石出了:这的确是一个由C99明确规定的“feature”而非编译器的"Bug"。

此时,仍然有一个疑问在我脑中挥之不去:

“为什么clang和IAR会允许在常量表达式中使用逗号运算符呢?”

在随后的搜索中,我大体找到了答案。实际上,也许正是如大家所感觉的那样——在一个常量表达式中禁用逗号运算符似乎并无必要——因此在随后的C++11标准中移除了对逗号表达式的禁令。clang和IAR显然因为某种原因(我猜是为了方便)在编译C代码(而非C++代码)时也同时移除了这一限制——这在某种程度上误导我们得出了“好学生GCC有Bug”的错误结论。更多细节和讨论,感兴趣的小伙伴可以看一下这篇帖子:

https://stackoverflow.com/questions/16576933/is-the-comma-operator-allowed-in-a-constant-expression-in-c11

【说在后面的话】


编译器是人类编写的,因此肯定会有Bug;但对于那些过于明显的“Bug”,如果对象是来自一个成熟的编译器,很可能反而是我们自己孤陋寡闻了。

这次事件给我的教训是:

  • 别着急下结论,多搜集证据
  • 作出重大决定前洗个澡可以让自己从盲目的情绪中清醒过来
  • 越是明显的东西,哪怕证据确凿,越是要小心可能有诈

对大部分常用的编译器来说,还是要给予足够的信任——因为有那么多人都在使用,如果有Bug,可能早就发现了。

本文分享自微信公众号 - 裸机思维(bare-metal),作者:GorgonMeducer 傻孩子

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 少年强则互联网强!编程猫这份报告藏着一座新金矿

    互联网上每天都有热点,相当一部分热点来自于娱乐八卦,在微博不断增加“服务器”时,明星们也炙手可热,疯狂吸金,或许正是因如此,经常会看到媒体报道,如今的年轻人特别...

    罗超频道
  • Python爬取糗事百科所有段子

    D:\Python\venv\Scripts\python.exe D:/Python/venv/test8.py

    py3study
  • 谈谈大学

    一个月前我答应一位读者写篇关于大学的文章,还留言说「一个月内如果没写,请提醒我」,后来一出差,我把这事完全抛诸脑后,期满后人家把截图发过来善意提醒,我一看,慌了...

    tyrchen
  • 源代码世界6—美人儿凯蒂

    “她还好吗?” 那大人眼睛里放出了一丝光,“你见过她吗?她可还记得卑微的码者,关河洲。”

    java达人
  • AI说人“画” | 模型?搞出“面相”的老祖宗早已看穿了一切

    大数据文摘
  • 上了名校才知道,我们到底输在哪里

    你看那北大毕业的还有卖猪肉的,清华毕业的还当保安,我手下刚招来的985大学的高材生,来了还不是要从打杂开始干?

    IT派
  • 程序员的笑话/漫画集锦-爆笑星期天

      (这是根据真实事情改编的哦。)   1. 发帖   一个女程序员 twitter 上发了一条 tweet:   昨晚梦见男朋友和别的女人在逛街,梦里我的第一...

    小莹莹
  • 犀牛鸟人物|北京邮电大学明悦:淡泊明志,宁静致远

    编者按 2013年,腾讯与CCF联合发起“犀牛鸟”基金,旨在为青年学者搭建“让伟大的梦想变成现实的影响”的平台,助力青年学者的创新和成长,并为提升人类生活品质做...

    腾讯高校合作
  • 凌晨三点,腾讯鉴黄师的漫漫长夜

    今天,带你认识几位鹅厂男女。 先麻烦你,关掉手边的电灯开关,找个舒适位置坐下,躺着也好。 让我与你说说,凌晨三点的腾讯,这些男男女女与长夜有关的故事。 再牛逼...

    腾讯举报中心
  • 周末分享:放眼30年,孩子们需要什么样的教育?

    大数据文摘
  • 【编译器玄学研究报告】第三期——“-O0” 就能逃出优化的魔爪么?

    很多人对编译器优化等级0("-O0")有着谜之信仰——认为在这个优化等级下编译器一定不会对代码进行不必要的优化——至少不会进行危险且激进的优化。让我们来看一个来...

    GorgonMeducer 傻孩子
  • 想从事大数据行业,什么专业比较好

    这是今天和客户吃饭的时候聊到的话题。某位客户的孩子在读高中,于是随口问陈老师:“想从事大数据行业的话,报什么专业比较好???”刚好一些同学也有类似问题,今天系统...

    接地气的陈老师
  • 像我这样的人

      我是一个特别有计划的人,也特别热爱学习,内心时刻充盈着奋发向上的心;尤其身为码农一族,更要活到老学到老!常常感叹余生苦短,时间宝贵,所以平日里,那些热门的剧...

    用户1615728
  • 大姨吗:从8000万女性数据,我们看到...

    大数据文摘
  • 长日无痕(一)

    注:本文写于2013年~所以文中的事件都发生在北京 (一) 又是一个闷热的周六,灰霾就像粘在天空一样,依旧笼罩着这个城市。带着小宝,我们三人游荡在蓝色港湾的儿...

    tyrchen
  • 闵海波:拥抱学前教育智能化,真正实现因材施教

    本文报告分享的主要领域是幼儿教育的智能化,主要针对幼儿园和家庭这两个场景。首先,对目前国内幼儿教育的现状进行了详尽剖析,然后对宾果科技公司在人工智能+幼教的战略...

    马上科普尚尚
  • 人工智能,能婴儿乎?

    万维刚老师在其专栏里介绍一篇关于人工智能和婴儿的文章,非常有启发性,今天跟大家分享一下。

    邱翔Alex
  • 学霸占据互联网过半江山

    用户1756920
  • How We Learn第七章 注意力的How What When 及抑制和训练 (长文+案例)

    仅仅突触可塑性的存在不足以解释我们物种的非凡成功。事实上,这种可塑性在动物世界中无处不在:即使是家蝇、线虫和海蛞蝓也有可修改的突触。如果智人变成了智人,如果学习...

    用户1908973

扫码关注云+社区

领取腾讯云代金券