首页 时政热点 科技头条 智能AI 安全攻防 数码硬件 开发者生态 汽车 游戏 社会热点 开源推荐 医疗健康 归档 标签 关于

减少C语言中的未定义行为

摘要

We're bad at marketing We can admit it, marketing is not our strong suit. Our strength is writing the kind of articles that developers, administrators, and free-software supporters depend on to know w...

the and that language what have for with The marketing
2026-10-09 1 阅读 约5分钟阅读 signa11
分享:
字号:
我们不擅长营销 我们必须承认,营销不是我们的强项。我们的优势在于撰写开发人员、管理员和自由软件支持者所依赖的文章来了解 Linux 世界正在发生的事情。请立即订阅以帮助我们继续这样做,这样我们就不必擅长营销。作为一名生物医学工程教授,Martin Uecker 或许并不符合 Kernel Recipes 中典型演讲者的形象。然而,他是 Linux 的长期用户,致力于开发用于控制磁共振成像 (MRI) 扫描仪的免费软件。他在会议上谈论了 C 编程语言、C 中未定义行为的具体问题,以及它是否最终可以成为内存安全语言。为什么到了 2026 年还要用 C 语言呢?他说,这仍然是一门伟大的语言。 C 是可移植的,长期稳定,提供快速编译,并且生成的二进制代码速度很快。 “所见即所得”;查看 C 代码并了解计算机实际要做什么是很容易的。有很多工具可以使用该语言,而 C 在必要时会避开。 C 确实有悠久的历史,这影响了我们今天所看到的语言,他说。 C89 标准必须应对各种硬件,包括具有符号数值或补码整数表示形式的机器、分段内存、奇异的指针表示形式以及令人惊讶的类型大小。例如,一些霍尼韦尔机器有九位字节。这使得编写一个能够编写可移植代码的标准的任务变得非常复杂。所采取的方法是根据抽象机器来定义语言的语义。所有操作都将像在该抽象机器上运行一样执行,这可能与实际硬件不完全匹配。程序的可观察行为一定是抽象机会做的事情。 “可观察”部分很重要:对被定义为可观察的易失性变量的访问必须完全按照抽象机进行;其他一切都必须产生相同的最终结果。该标准为编译器实现者提供了很大的自由度;只需保留可观察的行为。该行为的许多方面要么是未定义的,要么是实现定义的。这些不是可观察的行为,因此不会限制编译器实现者可以做什么。当然,还有其他规范可以限制编译器开发人员,而 C 标准则不然;其中包括 ABI 要求、POSIX 等标准或向后兼容性的需要。当程序执行不可移植或根本未由标准定义的操作时,就会出现未定义的行为。在这种情况下,C89 标准声明它对实现“不提出任何要求”。存在未定义行为的原因有很多。它允许实现支持扩展、管理与基于硬件的安全机制的交互,并执行积极的优化,同时允许忽略难以检测的错误。它明确赋予编译器忽略整类难以检测的错误的权利。 Uecker 说,问题在于该标准允许编译器执行任何操作来响应未定义的行为,直到调用鼻恶魔。根据编译器编写者的说法,如果程序根本包含任何未定义的行为,那么它就没有预期的语义。 C++23 标准进一步明确指出该标准对这些程序没有任何要求。这导致开发人员之间对于该语言的预期存在广泛的分歧。例如,如果您将整个结构清零(可能通过调用 memset() ),然后写入特定字段,那么如果您从该结构中的任何填充字节读取,会发生什么情况?它们可能包含与安全相关的数据吗? 2015 年的一项调查显示,对于这种情况下应该如何处理尚未达成共识。或者考虑这个简单的代码: extern int x; int f(int a, int b) { x = b ? 42:43;返回a/b;如果 b 为零,则 return 语句是除以零,这是未定义的行为。在这种情况下,编译器是否有权完全省略测试并只执行 x = 42 ?毕竟,b = 0 的情况没有预期的语义,因此可以忽略。有些编译器可以做到这一点。在未定义行为的情况下,存储到 x 是不可观察的行为。但现在考虑这种情况: extern void g(int x); int f(int a, int b) { g(b ? 42 : 43);返回a/b;这似乎是相同的情况,编译器有权删除测试并仅将 42 传递给 g() ,并且某些编译器已经这样处理 - 但编译器的行为是一个错误。想象一下 g() 的定义,如果 b 为零,则调用 exit()。在这种情况下,除法永远不会发生,程序的行为也不会发生
这篇文章对您有帮助吗?

订阅66必读

每日精选科技资讯,直达你的邮箱