为什么C不会阻止您犯错误



简而言之:因为我们这么说。



:)



好吧,亲爱的读者,这篇文章的解释太短了,我挑衅性的话需要解释。



C语言委员会的会议原定于德国弗莱堡举行,但由于明显的原因未能共同发展,该会议于8月7日结束。一切顺利,我们在各个方面都取得了进展。是的,我向您保证,我们的确取得了进步,C并没有死。



我还要提到我已经成为C项目的编辑,因此在将标题作为一个懒惰的人(无意于“尝试改进”)的愚昧陈述之前,我想向您保证,我实际上正在非常努力地确保C可以为了满足开发人员的需求,而不必为了构建或多或少漂亮而有用的库和应用程序而拧紧50个特定扩展。



但是我还是这么说的(C语言永远不会阻止您犯错误),这意味着我必须证明自己是有道理的。我们可以用一堆C代码查看成千上万的CVE和相关的票证,或者我们可以使MISRA大力检查每个C功能是否存在潜在的误用(您好,K&R原型声明...)或存在与可移植性和不确定行为有关的更复杂,更有趣的错误。但是,我们改为阅读原始资料,即委员会本身所说的话。



哦,是时候买些爆米花了吗?



没有亲爱的读者,把爆米花放在一边。与所有ISO程序一样,我不能引用任何人的话,并且本文并不旨在羞辱任何人。但是,我将解释为什么从不排除在标准兼容的ISO C文档中我们很容易认为不良行为的某些原因。让我们从Philipp Klaus Krause博士的文档开始:



N2526,将const用于不会被修改的库数据



N2526是一个非常简单的文档。



, , , . — , ! …


我同意,这并不完全相同,但是亲爱的读者,我相信这个想法对您来说似乎很合理。将该文件付诸表决时,几乎没有人反对。后来,有几个人强烈反对,因为该提议破坏了旧法规。当然,这很不好:即使我考虑添加constC语言中没有ABI会受到创新的影响。C(它的实现)甚至不注意限定词,我们怎么能破坏某些东西?让我们看一下为什么有些人认为这将是一个巨大的变化。



语言C



或者,就像我喜欢的那样,“类型安全性是针对失败的语言的。” 是的,太冗长了,所以让我们在“ C”处停止。您可能想知道为什么我说像C这样的语言不是类型安全的。毕竟这里是:



struct Meow {
    int a;
};

struct Bark {
    double b;
    void* c;
};

int main (int argc, char* argv[]) {
    (void)argc;
    (void)argv;

    struct Meow cat;
    struct Bark dog = cat;
    // error: initializing 'struct Bark' with an expression of incompatible type 'struct Meow'

    return 0;
}


老实说,对我来说,这对我来说就像是强类型安全。因此,一切变得更加辛辣了:



#include <stdlib.h>

struct Meow {
    int a;
};

struct Bark {
    double b;
    void* c;
};

int main (int argc, char* argv[]) {
    (void)argc;
    (void)argv;

    struct Meow* p_cat = (struct Meow*)malloc(sizeof(struct Meow));
    struct Bark* p_dog = p_cat;
    // :3
    
    return 0;
}


是的,C标准允许两种完全独立的指针类型相互引用。大多数编译器都会对此发出警告,但是该标准要求您接受此代码,除非您解开-Werror -Wall -Wpedantic等等等等。



实际上,编译器可以接受此操作而无需显式强制转换:



  • volatile (谁根本需要这些语义?!)
  • const (在这里写任何只读数据!)
  • _Atomic (线程安全!)


我并不是说您不应该做所有这一切。但是,当您使用C语言编写时-在其中创建具有完全无法理解的变量名的500-1000行的函数非常容易-Infa Sotka,您主要使用指针,并且通常在基本语言方面缺乏安全性。注意:这违反了限制,但是已经编写了太多的旧代码,每个实现都以某种方式忽略了限定符,因此,将不会阻止您的代码进行编译(感谢@fanf!)!在这种情况下,您可以在编译器的帮助下轻松识别每一个潜在的故障,并且您会收到警告,但是为了使编译器知道您真正想做的事情,不需要进行强制类型转换。但是,更重要的是,追随您的人类也不会理解您打算做什么。



您需要做的就是删除该功能-Werror -Wall -Wpedantic,您将准备犯下多线程,只读模式和硬件寄存器的罪行。



现在一切都好吧?如果有人删除了所有这些警告和错误标志,他们将不会在乎您执行哪种错误操作或愚蠢的操作。最终,这些警告对于ISO C合规性而言是完全不相关且无害的。



我们正在考虑打破警告



是。



这是C开发人员以及在较小程度上C ++开发人员习惯的一个特殊地狱。警告很烦人,而且如实践所示,包括-Weverything/W4,也很烦人。使用“保留”名称(如孩子们说的“ lol nice one xd !! ”)和“此结构具有填充” ,将变量警告隐藏在全局名称空间中(感谢,现在所有标头和C库都是问题)因为您使用了alignof“(...,是的,是的,我知道她有填充,我明确要求添加更多填充,因为我使用过alignof,MR。COMPILATOR)-所有这些都需要很多时间。



但是这些是警告。



即使它们很烦人,它们也可以帮助您避免出现问题。在传达我的意图和避免错误时,我可以无耻地忽略所有限定词并忽略各种读,写,流和只读安全性这一事实。甚至旧的K&R语法也导致工业和政府代码库中的错误,因为用户做错了什么。他们这样做并不是因为他们是糟糕的程序员,而是因为他们使用的代码库通常比他们老,并且准备与之抗衡。数百万行。不可能把整个代码库都掌握在脑子里:约定,静态分析,高级警告和其他工具都可以做到这一点。不幸,



每个人都希望有一个没有警告的代码。



这意味着当GCC开发人员使警告对潜在的问题情况更加敏感时,维护人员(而非原始开发人员)会意外地从旧代码中收到几GB的粗体日志,其中包含许多新警告和各种不同的内容。他们说:“这很愚蠢,该代码适用于YEARS,所以为什么现在GCC会抱怨?”也就是说,即使您const在签名上添加功能,即使在道德上,精神上和实际上是正确的,也可以避免。 “破坏”人员的意思是“现在他们必须寻找具有可疑意图的代码”。该代码可能会(出于不确定行为的考虑)破坏您的芯片或损坏您的内存。... 但这是当今C开发人员职业伴随的另一个问题。



年龄作为衡量质量的标准



有多少人甚至认为自己sudo具有诸如“ -1或整数溢出可以访问所有内容”之类的原始漏洞?有多少人认为Heartbleed可能是一个真正的问题?有多少游戏开发人员甚至不使用移相器就交付了“小型”机顶盒库,而没有意识到这些库包含比您想象的更重要的输入漏洞?我并不是在批评所有这些事态发展或其作者:它们为我们提供了至关重要的帮助,全世界数十年来一直依靠这些帮助,而在出现一些大问题之前,往往很少或根本没有支持。但是,那些崇拜这些事态发展并独自承担责任的人,然后从自己身上渗出一句有毒的话,说明了幸存者的错误:



, ?


保持向后兼容和“放松”的原则作为C的最高理想,在该行业中生存时间足够长的人们开始将年龄与质量等同起来,例如代码库就是一桶酒。使用该代码的时间越长,时间越长,则葡萄酒越细腻,越精致。



不幸的是,所有事情并没有那么浪漫和可爱:到处都是漏洞,并且有大量的安全漏洞,所有这些技术债务每天都变得越来越危险。随着时间的流逝,所有系统都变成半衰期,蓬头垢面和部分不受支持的腐烂堆。他们被点缀并赋予了贵族精神,但实际上,他们是木乃伊,只是在等待笨拙地戳戳,然后它们溃烂的,前卫性脓肿会因其美丽而古老的肉毒杆菌毒素而爆炸并充斥您的应用程序。



嗯...恶心。但是标准C呢?



在作为参会者的我(非常短)的任期内,我注意到的问题是我们将向后兼容性放在了最重要的位置。对于那些甚至今天正在迁移到C的用户,我们坚持使用旧的应用程序及其用例,并剥夺了改善C代码安全性或艺术性的机会。 ,他可以将其关闭。这些警告(不是错误)并非一无是处:抽象的C机器不需要为了进行诊断,ISO C允许在严格的汇编模式下接受此类代码。这将帮助全世界摆脱公开声明的API,“更改我们提供给您的内容是未定义的行为”。



但是,在给出此文档后,由于“我们不能引入新的警告”,我们改变了主意。



反对该提议的论点是:“如果我们更改这些签名,将会编写很多代码,这些代码将被破坏。”同样,这会限制行为方面的警告更改(请记住,隐式转换删除了限定词-甚至_Atomic-即使违反了限制,也完全符合ISO C的要求。如果是这种情况,每位编译器作者都将引入“ Rusts in Rust,仅警告”之类的内容,为人们提供一个“稳定”的测试基准。这项建议不是新的:我已经阅读了Coverity工程师提供的有关生成新警报以及用户如何对其做出反应的类似文档。难以管理开发人员对新警告和其他事项的“信心”。要花很长时间才能说服人们他们的用处。就连约翰·卡马克(John Carmack)也必须努力工作,才能从自己的静态分析工具中获取正确的警告和错误,以进行开发,然后他得出结论:“不使用它是不负责任的



但是,我们委员会不同意在const这四个功能中增加返回值的功能,因为这将对潜在危险的代码添加警告。尽管有充分的证据表明无辜的监督和在传递错误类型时存在严重漏洞,我们仍反对过时的K&R语法。我们几乎在预处理器中添加了未定义的行为,只是为了降低它的作用,而只是使C实现“应有的表现”。由于向后兼容,我们始终走在最前沿,以免犯明显的错误。亲爱的读者,这个让我最担心S的未来。



标准C不能保护您



没错:程序员告诉您什么或对您低声说什么都没关系。 C语言指导委员会非常明确。即使此代码可能很危险,我们也不会在您的旧代码中添加新的警告。我们不会阻止您犯错误,因为它会破坏您的旧代码如何工作的想法,这是错误的。我们不会帮助新手编写更好的C代码,也不会要求您的旧代码符合任何标准。每个新功能都是可选的,因为我们无法想象强迫编译器作者坚持更高的标准,或者期望我们的标准库开发人员提供更多的功能。



我们会让编译器对您说谎。我们会骗你的代码。当一切都出错时-会出现错误消息:“哦,发生了某种垃圾”,这将导致数据泄漏-我们将严肃地摇头。我们将分享我们的想法并为您祈祷,然后说:“好可惜。” 确实,感到羞耻……



亲爱的读者,也许有一天我们会解决它。



All Articles