为什么我相信使用 C 语言可以保证内存安全?

Posted CSDN资讯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我相信使用 C 语言可以保证内存安全?相关的知识,希望对你有一定的参考价值。

【CSDN 编者按】在研发过程中,编程语言的优劣对应用程序的性能有着巨大的影响。近些年间,随着应用程序规模的扩大,越来越多的公司开始关注其安全性能,如 Google、微软、亚马逊等科技巨头纷纷开始推荐 Rust 这类为安全而生的语言,欲取代过往的 C/C++,甚至美国国家安全局也于不久之前发布建议:弃用 C/C++,使用更安全的 Rust、C# 等!

不过,在本文作者、也是一位软件开发者、计算系统设计师看来,Rust、C++ 都没有 C 香,以下他列出了 7 大理由!

原文链接:https://gavinhoward.com/2023/02/why-i-use-c-when-i-believe-in-memory-safety/

声明:本文为 CSDN 翻译,未经允许,禁止转载。

作者 | Gavin Daniel Howard

译者 | 弯月     责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

最近,美国国家安全局发布了一些关于软件内存安全的建议(https://media.defense.gov/2022/Nov/10/2003112742/-1/-1/0/CSI_SOFTWARE_MEMORY_SAFETY.PDF):

其鼓励多个组织将编程语言从 C/C++ 转为使用内存安全的语言,如 C#、Rust、Go、Java、Ruby 和 Swift,主要原因是这样可以帮助软件开发者和使用者预防并缓解软件内存安全问题,这些问题占可利用漏洞的很大一部分。

这份文档引起了很大骚动。其实,这些都是常识性建议,如非有充分的理由,我们都应该遵循这些建议。

不过,C++ 之父 Bjarne Stroustrup 认为 C++ 并没有那么糟,他表示美国国家安全局忽略了 C/C++ 三十多年来的进步。

Bjarne Stroustrup 还说:

“例如,Microsoft Visual Studio 分析器和内存安全评测器已能提供大部分的 CG 支持,而现在任何优秀的静态分析器(例如 Clang tidy 就提供了部分 CG 支持)都可以设置为提供这样的保证……”

但我非常不赞同这个观点。事实上,我认为 C++ 的复杂性使其比 C 更糟。

美国国家安全局人员 Jimmy Hartzel 如此评价,我非常同意他的看法:

“就内存安全而言,‘大部分’是完全不够的,而‘可以设置为’实际上也没有价值。从根本上来看,关键的一点是 C++ 的内存安全仍在积极开发中,已经很接近其他的语言了。同时,Rust(和 Swift、C#、Java 等)已经实现了内存安全。”

虽然我不太喜欢 Rust,但不可否认的是,Rust 是当前 C 或 C++ 程序员的最佳选择。

然而,我的当前项目仍然是用 C 语言编写的。

为什么我这么喜欢 C 语言?理由如下。

文档

第一个理由是,我可以记录一切。

从很多方面来说,Rust 很棒,而且该语言本身也很擅长提供代码的文档。

例如,对于显式指针类型,很容易看出如何正确使用函数的参数。

C 只有值和指针类型。指针可以动态分配或指向祖先栈中的某些元素。但也可以是完全不相关的东西。

Rust 具有更好的类型安全。你给 C  void*,它会欣欣然接受这个有可能指向任何东西的指针。

客观地说,从这些方面来看,Rust 的表现更好。

我通过各种方式来写文档:

  • 记录所有能想到的假设,而且通常是在代码中利用断言来记录。

  • 记录每个函数及其参数和用途。

  • 记录每个结构、每个字段、每个枚举。

  • 我还写了很多设计文档和开发文档。

如此一来,我不仅享受到了 Rust 的许多优点,而且还有很好的文档,这对我的用户来说无疑是一件好事。

我并不是说 Rust 更好,只不过这样可以迫使我编写文档。

单独工作

其次,我需要独自将脑海中的想法变成代码。

Rust 非常适合团队,因为它消除了许多团队合作编写代码时的风险因素。

然而,我是单独工作,因为我喜欢把代码记在脑子里,与他人一起工作则意味着部分代码只在他们的脑子里。

这也意味着,语言越大,我脑子里留给代码的空间就越少。

不幸的是, 对于我的小脑袋来说,Rust 太大了。


我觉得 Rust 不够有趣

这引出了下一个原因:我不喜欢 Rust。

注意,这纯粹是个人喜好, 我并不是说 Rust 不好。

我使用过很多语言编写代码:

  • C

  • C++

  • Rust

  • Haskell

  • Go

  • Python

  • php

  • Ruby

  • Zig

  • Bash

  • POSIX sh

  • bc

  • dc

  • javascript

  • Clojure

  • Java

  • C#

  • Various assembly languages

  • GLSL

  • Common Lisp

  • Racket

  • Julia

  • Tcl

  • Vim script

此外,我还研究过以下语言:

  • ML

  • OCaml

  • HAL/S

  • Pascal

  • Objective-C

  • MATLAB

  • Modula-2

  • Coq

  • Isabelle/HOL

  • Ada/SPARK

等等。

若论编程的乐趣,我讨厌所有这些语言。除了 C 语言。

没错,C 是唯一我真正喜欢的编程语言。

我不是在说笑,相较于其他语言,我用 C 编写代码的效率高出几个数量级。我的脑子只适合用 C 思考,我也不知道为什么。

这对我来说非常重要。根据以往的经验,我是一个很容易情绪化的程序员,如果周遭的环境不合适,我就无法有效地编程。

如果累了,我就无法有效地编程。

如果一边写代码一边听音乐,我也无法有效地编程。

如果被一项任务压得喘不过气来,我就无法有效地编程。

如果无法想象整个软件,我就无法有效地编程。

如果老婆跟我闹脾气,我就无法有效地编程。

最后,我无法使用不喜欢的语言进行有效的编程。

我曾经拒绝过一份工作邀请,因为他们使用的是 C++,而我讨厌 C++。

所以我不使用 Rust 或其他内存安全语言,因为我已经尝试过,但我无法使用这些语言有效地编程。

我知道这是个人问题,跟 Rust 无关。

但是,我很擅长 C 语言,虽然并不完美。

自律

这引出了我的下一个原因:我很自律,而且能通过这种能力编写高水平的 C 代码。

我的自律可以通过多种形式表现出来:

  • 我只使用无符号整数,以此避免未定义的行为。我甚至会使用无符号整数模拟有符号的二补数。

  • 如果我编写的函数的返回值没有被使用,我会使用一个宏让 Clang 报错。

  • 我使用 -Wall、-pedantic 和 -Werror 来运行 Clang 和 GCC。 这意味着,我在开发的阶段就消除了警告。

  • 我甚至用 -Weverything 运行 Clang,除了 -Wpadded,因为我不关心结构中的填充(我会仔细安排它们的布局);还有-Wc++98-compat,因为我不关心 C++ 98 兼容性 ,因为它与 C11 stdalign.h 相互干扰。

  • 我会在测试套件中运行健全性测试,并消除一切错误。是的,一切,甚至是误报。

  • 我会在测试套件中运行 Valgrind,并消除一切错误。

  • 我会在测试套件中运行 Helgrind ,并消除一切错误,甚至是误报。

  • 我会运行静态分析,并消除一切错误,甚至是误报。

你不了解一次又一次地运行 Valgrind、Clang、ASan 或 TSan,一次又一次地消灭 bug,最后什么都不剩下的感觉有多么好。

我感到无比的幸福和满足。

这也算是一种编程强迫症吧。

经验

下一个原因:经验。

我花了很多时间用 C 来解决各种 bug,因此我花在 C 上的时间远超其他编程语言。我对 C 语言的了解远胜其他语言。

这种经验上的差距很难弥补。如果从今天开始使用 Rust,我相信我需要大约十年的时间才能像 C 一样精通 Rust。

为此,我需要付出大量的时间。

编译时间

下一个核心原因:编译时间。

我知道如今 Rust 已经得到了很大提升,非常棒。

但 Rust 仍然很慢。我的电脑是 16 核的,在相同的时间里,用 C 语言我可以构建整个项目(包括测试套件),而用 Rust 我只能构建一个文件。

另一个方面是 C 文件包含模型,尽管不尽如人意,而且并行非常尴尬。Rust 很好,只不过还没有那么好。


自定义内存安全

下一个原因是,在使用 C 语言期间,关于内存安全,我已经开发出了自己的软件设计风格。

  • 我使用特殊的数组类型来代替普通指针,这些数组会存储自己的边界。

  • 我有一些宏,在访问数组索引时会进行边界检查。

  • 我有一些宏,在计算数组指针时会进行边界检查。

  • 我有一个栈分配器,我可以借助这个栈分配器在每个平台上实现 alloca() 。

  • 我可以借助栈分配器将每个分配都放在同一个栈帧内。

  • 栈分配器知道析构函数,因此它会在释放内存时调用析构函数。

  • 如果我调用一个特定的函数,栈分配器会释放作用域内分配的所有内存。

  • 如果我调用一个特定的函数,栈分配器会释放函数中分配的所有内存。

  • 我使用结构化并发,这意味着如果将数据传递给新线程,我可以确保线程的寿命不会超过传递给它的数据。

简而言之,每条数据都可以追溯栈中特定位置上的祖先,数据永远不会传递给生命周期有可能超过自己的对象。

也就是我们常说的RAII(Resource Acquisition Is Initialization,资源获取即初始化),这意味着我能够控制生命周期和所有权,可以使用借用,以及我用 C11 重写了 Rust 的精华部分。

一般来说,让编译器静态地强制一切规则是最好的。

所有这一切都意味着,内存的分配和释放必须在同一个函数内,或者作为父对象的一部分被释放。

这大大减少了双重释放和释放后使用。因为我的所有代码都采用了这种写法,无一例外,所以我可以通过充分的测试来消除所有错误。

此外,边界检查可以大幅减少缓冲区溢出。

当然,引用 Jimmy Hartzell 的一句话:任何不完美的东西都不够好。

换句话说,我使用的不是 C 语言,而是内存安全的 C 语言。


总结

所以,上述就是我坚持使用 C 语言的全部理由。

当然,你有可能不同意我的看法。

只不过,我这个人遇到不喜欢的软件,就无法享受编写的过程。而我只喜欢 C 语言。

所以,对我个人而言,坚持使用 C 仍然是不二的选择。


 

☞自学编程,从月薪500到年薪150万,46岁程序员的IT成长之路
☞ChatGPT 上线 70 天,微软用它改写 Bing、Edge后,市值一夜飙涨 5450 亿元!
☞TIOBE 2 月编程语言榜:C++ 势不可挡

以上是关于为什么我相信使用 C 语言可以保证内存安全?的主要内容,如果未能解决你的问题,请参考以下文章

Rust为什么我建议你学一下 Rust | Rust 初探

Rust为什么我建议你学一下 Rust | Rust 初探

eng和rus的区别

面试官问:怎么保证线程安全在对象内存分配过程中不出问题?emmmm 让我想想

面试官问:怎么保证线程安全在对象内存分配过程中不出问题?emmmm 让我想想

Rust编程语言入门