为什么C语言永远不会阻止你犯错误

2020-08-10 20:47:39

好的,好的,这太短了,不能写成一篇文章,亲爱的读者,我的煽动性的话需要一个解释。

C委员会会议原定于德国弗莱堡举行,但由于(*模糊地向外界示意*)而没有在那里举行--于8月7日星期五结束。进展相当顺利,我们在各个方面都取得了很好的进展。是的,我们取得了真正的进展;我保证我们取得了进展,而这并不是一种死亡的语言。

顺便说一句,我还成为了C的项目编辑,所以在您认为这是一个懒得“让事情变得更好”的人不知情的咆哮之前,让我向您保证,我确实投入了令人难以置信的资金来确保C可以开始满足开发人员的需求,而不需要50个特定于供应商的扩展来构建远程漂亮/可用的库和应用程序。

尽管如此,我还是提出了一个主张(C语言永远不会阻止您犯错误),因此我需要证实这一点。我们可以看看数以千计的CVE和大量C代码的固有问题,或者米斯拉需要严格监管每一个潜在的C功能,以防止滥用(你好,K&;R Prototype声明…)。,或者其他与可移植性和未定义行为相关的更复杂和有趣的错误。但相反,我们只是要从马的嘴里说出话来:委员会本身。

不,亲爱的读者,把爆米花收起来。与所有ISO程序一样,我不允许逐字引用任何人的话,这也不是一篇指名道姓的文章。然而,它将解释为什么在符合ISO C的标准中,我们可以很容易地诊断和识别为不良行为的事情永远不会消失。我们将从菲利普·克劳斯·克劳斯博士的一篇论文开始:

N2526是一张非常简单的纸。“图书馆返回的一些数据在道德上、精神上,甚至在它的实现上都是常量的。这是不确定的行为,不应该写进去,所以让我们停止互相取笑,给那个坏男孩的手指戴上戒指吧!“…。好吧,这不完全是它所说的,但我相信这个想法对你来说是有意义的,亲爱的读者。最初,当对这篇论文进行投票时,几乎没有人投反对票。然后,一些人表达了他们对这份文件的强烈反对,因为它打破了旧的准则。当然,这很糟糕:连我的呼吸都屏息了,我费力地想着这一点--加上Const?C没有可能受此影响的ABI,C甚至不尊重限定符,我们怎么会破坏东西呢?!那么,让我们来谈谈为什么这在某些人的眼中会是一个突破性的变化。

或者,正如我喜欢称呼它的那样,“类型安全是为失败者语言准备的”。当然,这是一个很长的口子,所以“C”就够了。您可能会感到奇怪,为什么我会说像C这样的语言没有类型安全。毕竟,。

Struct喵{int a;};struct Bark{doub;void*c;};int main(int argc,char*argv[]){(Void)argc;(Void)argv;struct喵cat;struct Bark dog=cat;//错误:使用不兼容类型的表达式初始化';struct Bark';struct Meow';return 0;}

我的意思是,让我们实话实说:在我看来,这是很强的类型安全,吉姆!当然,从这里开始只会变得更辣:

#include<;stdlib.h>;struct喵{int a;};struct Bark{doub;void*c;};int main(int argc,char*argv[]){(Void)argc;(Void)argv;struct喵ow*p_cat=(struct喵*)malloc(sizeof(struct喵));struct Bark*p_dog=p_cat;//:3返回0;}。

F̷͖̙͈̗̯̙͓̲̻̩̱͖̺a̧̪̳̠̰̱͎̖̙̳̱͎̯̦̟͟m̶͓͔͉̺̩̙̖̞̼͈̖͇͇͇͚d͎̀̕͢es̵̱͙̪͓͖͇̩̳̻͉͉̹̣͈͜͝t͖͈͉̩̭̙̺̬r̤̟̫͔̩̰̗̤͔̭̰̞̦̺ͅo̝̹̻ͅy̸͎͎͚̖̤̣̥̭̼̳͎e̘̟̰̣̫̹͇͍̦͓ͅr̙̙̳̠͔̙̖̯̺ͅ҉̣̞̟a̪̲͎͇̣̠͖͖̮̥ͅl̷̛͈̦̰̜͍̲̟̬̘̝͕̰̮͠l̮̯̘͉̦͔̘̲̰̤͔̬̲͔o̵̞͟͞o̷r̡̰͇͎̞̝͍͍̥̗̖͖̲̮i͕͔̭̹̪̳͙͍̩͇̘̬͕̯̕͢ͅd͚o̢̳̱̙͠ͅg̴̨̦̬̥͓̯̩͞,̷̸̬͕͖͎̺͈̰̳̮͓̞̜̫̫̩̫̝͎̩̠̦̭̠̟o҉̛͢f̫̝͇。

是的,在符合C的标准中,两个完全不相关的指针类型可以被设置为彼此。大多数编译器都会警告,但这是符合标准的ISO C代码,除非您启动-Werror-Wall-Wpedtic等,否则不能拒绝该代码,依此类推。

事实上,编译器在不进行显式强制转换的情况下需要接受的其他与指针相关的内容包括:

现在,请注意,我并不是说你根本不应该做这些事情。但是当你在C中工作时-在C中很容易得到一个5001000行的函数和没有描述它们所用的WTF的变量名-硬事实™是你主要使用指针,就核心语言而言,你根本就缺乏任何程度的安全性。这里的每一个潜在故障都可以用编译器轻松地诊断出来,它们都会发出警告,但它们永远不会要求您强制转换来与编译器沟通,表明您是认真的、真的是认真的。这也意味着--更重要的是--追杀你的人类也不会知道你是否有意这么做。

所有人需要做的就是剥离-Werror-Wall-Wpedtic的构建,然后您就会在多线程、只读和硬件寄存器犯罪中大打出手。

现在,这是公平的,对吗?如果有人去掉了所有这些警告/错误标志,他们显然不会在意您犯下的任何失礼或愚蠢的失言。这意味着,归根结底,这些警告对于标准符合性(ISO C)而言是无关紧要的,也是无害的。然而…

这是一种特殊的地狱,C开发人员已经习惯了这一点,而C++开发人员在较小的程度上也是如此。警告被视为烦人的事;而且,正如曾经打开WEverything或/W4所显示的那样,它们很好地证明了这一点。跟踪来自全局名称空间中的变量的警告(感谢,每个头文件和C库现在都是一个问题),使用“保留的”名称(正如孩子们说的,“LOL NICE ONE XD!”)和“这个结构有填充,因为您使用了ALIGOF”(…。是的,是的,我知道它有填充,我明确地要求它增加我的填充,因为我使用了alignof(编译先生)都是非常浪费时间的。

即使它们令人讨厌,它们也能防止问题。事实上,我可以轻松地忽略所有限定符,并丢弃所有形式的读、写、线程和只读安全性,这在通信意图和防止bug方面是一个严重的问题。即使是旧的K&;R语法也会在工业和政府代码库中产生错误,因为用户做了错误的事情。这并不是因为他们是狗屎程序员:而是因为他们跨越了代码库,有时比他们的年龄还老,并准备承担数百万的技术责任。您不能将整个代码库保存在您的头脑中:这是约定、静态分析、高警告级别以及一切都应该要对抗的。不幸的是,

这意味着,当GCC开发人员使警告对潜在问题更加敏感时,维护人员(而不是最初的开发人员)将突然收到大量的几千兆字节的日志,其中包含他们旧代码中所有最新的警告和其他内容。“这太愚蠢了”,他们说,“代码一直在为Y、E、A、R、S运行,为什么GCC现在还在抱怨呢?”这意味着,即使是像将const添加到函数签名这样的事情,即使在道德上、精神上和事实上都是正确的,也可以避免这样做。“破坏”的人是“他们现在不得不看那些有可疑意图的代码”。这是一段代码,它可能会使您的芯片崩溃或损坏您的内存,这是由于未定义的行为所带来的痛苦。但这是在今天的生态系统中成为C开发人员的另一个问题,真的。

有多少人会猜到sudo有一个令人瞠目结舌的简单漏洞,比如“-1或整数溢出让您可以访问所有东西”?有多少人认为心脏出血会是一个真正的问题?有多少游戏开发人员在发布机顶盒的“微型”库时,没有对它们运行模糊程序,并且意识到它们包含的可利用的输入漏洞比他们想象的要多呢?这并不是在挖苦其中任何一段代码或它们背后的程序员:它们提供的是世界几十年来一直依赖的重要服务,在大问题爆发之前,通常几乎没有任何人提供支持。但是,崇拜和部署这些代码的人最终在生存偏见的吉祥物下发酵了一句有毒的谚语:

“这个代码这么老,这么多人都在用,怎么会有问题呢?”

将向后兼容和“不麻烦”作为C代码的最高理想,那些在行业中存活足够长的人开始将年龄等同于质量,就好像代码库就像是酒窖里的一桶酒。密码用得越久、越久,酒就越好、越可口。

不幸的是,现实远没有那么浪漫和可爱:到处都是你无法动摇的错误,满满一筐的安全漏洞,这些技术责任只会变得越来越危险。每个系统都进化成一个半活的、未寻址的、部分未维护的腐烂外壳。他们被打扮得漂漂亮亮的,并被赋予了高贵和伟大的外表,而实际上它是一具防腐的身体,在它溃烂的、陈旧的脓疱破裂并用它精致的肉毒杆菌填满你的申请之前,只是等待着被戳向错误的方向。

在我作为成员的任期(短得令人难以置信)中,我注意到的问题是,我们更喜欢向后兼容,而不是将旧的应用程序及其用例归因于提高C代码的安全性、安全性或工艺的任何机会,即使是今天也是如此。克劳斯博士的论文很小,几乎毫无争议:如果有人不喜欢这些警告,他们可以把它们关掉。它们是警告,而不是错误,原因是:不需要诊断,ISO C要求代码在最严格的构建模式下被接受,这将帮助世界上的其他人摆脱对API的依赖,这些API清楚地声明“修改我们返回给您的内容是未定义的行为”。

然而,在提出“我们不能引入新的警告”作为理由后,我们无论如何都把我们对该报的意见翻了过来。

反对的论据本质上是“如果我们改变这些签名,会有太多的代码会崩溃”。这再次将警告中的更改定义为行为中断(请记住,删除限定符-EVEN_ATOM-的隐式转换是完全有效的ISO C)。如果是这样的话,每个编译器开发人员都将不得不引入一些类似于Rust时代的东西,但只是为了警告,以便给人们一个“稳定”的集合来始终进行测试。这并不完全是一种新的观点:我读过Coverity自己的一些工程师关于他们产生新警告的处理方式以及客户对这些警告的反应的论文。在新警告和其他事情中管理“开发人员信心”是一项艰巨的工作。引导开发人员了解它的用处需要很长时间。即使是John Carmack也花了一些时间从他的静态分析工具中获得适合他开发的一组适当的警告和错误,然后得出结论说“不使用它是不负责任的”。

然而,作为一个委员会,我们很难将const添加到4个函数返回值中,因为这会给潜在的危险代码添加警告。我们反对摒弃旧的K&;R语法,尽管有确凿的证据表明,传递了错误的类型的开发人员既有无辜的失误,也有明显的漏洞。我们几乎向预处理器添加了未定义的行为,只是为了尽最大努力制作一个定制的C实现“做正确的事情”。出于向后兼容的原因,我们总是徘徊在做客观上错误的事情的边缘。亲爱的读者,关于C的未来,这是我最害怕的。

别搞错了:无论程序员告诉你什么,或者人们在你耳边窃窃私语什么,C管理机构的行为都是非常清楚的。我们不会在您的旧代码中引入警告,即使该旧代码可能会做一些危险的事情。我们不会引导您远离错误,因为这可能会动摇您的旧代码所做的事情实际上是错误的表象。我们不会让新程序员更容易编写更好的C代码。我们不会要求您的旧代码遵守任何标准。我们添加的每一个新特性都将是可选的,因为我们不可能想象让编译器编写者遵循更高的标准,也不可能对我们的标准库供应商有更高的期望。

我们会让编译器对您撒谎。我们会对你的代码撒谎。当事情真的变得糟糕时--一个错误,一个“糟糕的卡普西”,一个数据泄露--我们会庄严地摇摇头。我们会献上我们的思念和祈祷,并说“好吧,那太可惜了”。真遗憾,确实是…