從記憶體安全形度審視 C++、Zig 和 Rust

banq發表於2024-07-08

一般來說,C++ 讓程式設計師可以自由地做任何他們想做的事情。Circle C++ 提供了一個令人信服的解決方案,可以增強 C++ 的記憶體安全性,併為 C++ 提供出色的附加功能,這些功能可以輕鬆(最重要的是,可以逐步適應現有的 C++ 程式碼庫)。

Rust 提供了出色的預設值和嚴格的記憶體安全性。它確實是一種出色的程式語言,但它的學習曲線也很難,因為借用檢查器等概念對於 C++ 老手來說可能是一個陌生的概念。

Zig 提供了平衡、合理的記憶體安全性(使用分配器的不干預方法),總體而言,與 C++ 或 Rust 相比,它是一種更簡單的語言。它還提供了與 C/C++ 程式碼庫的出色整合,因此可以很容易地將其新增到現有程式碼庫中。

如何比較記憶體安全性
根據 Sean 的工作,記憶體安全可以分為五類:

  1. 生命週期安全
  2. 型別安全 — 空型別
  3. 型別安全——聯合多樣性
  4. 執行緒安全
  5. 執行時安全

為了本文的目的,我將僅研究生命週期安全、型別安全(null 和 union)和執行時安全。

生命週期安全
 Circle C++ 程式碼:

feature on safety 
# include  <cstdio> 

int  main () safe { 
  int ^a; <font>// 未初始化的借用<i>
  { 
    int b = 10 ;
// b 處於活動狀態<i>
    a = ^b;
// 儘管 a 似乎是從 b 可變借用,但是在當前作用域內它未被使用,因此 a 未初始化。<i>
   } 
  int c = *a; 
  unsafe printf (
"%d\n" , c) ; 
  *a = 11 ; 
  unsafe printf (
"%d\n" , *a) ; 
}

  • feature on safety: 這是 Circle C++ 編譯器的一項功能,我們在其中新增了 Circle C++ 編譯器的特定功能。
  • main 函式的安全限定符safe。 這讓我們可以為特定的函式指定安全性。 我認為這是在現有大型 C++ 程式碼庫中引入嚴格安全性的絕佳方法。 這將允許在現有專案中逐步新增嚴格的安全標準,而無需進行重大的完全重寫,因為重寫將導致編譯器錯誤

Zig程式碼:

const std = @import(<font>"std");

pub fn main() !void {
    var a: *i32 = undefined;
    {
        var b: i32 = 10;
        a = &b;
        std.debug.print(
"address of b is: {s}\n", .{&b});
    }
    std.debug.print(
"a is pointing to: {s}\n", .{a});
    const c: i32 = a.*;
    std.debug.print(
"{d}\n", .{c});
    a.* = 11;
    std.debug.print(
"{d}\n", .{a.*});
}

結果表明程式碼中明視訊記憶體在使用後釋放漏洞,因為即使“b”超出範圍後,“a”仍持有記憶體地址“b”。為了緩解這一問題,Zig 通常建議採取以下措施

  1. 不要使用指標作為堆疊分配的變數
  2. 使用allocator分配器在堆上分配記憶體

修改後:
const std = @import(<font>"std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
    defer {
        _ = gpa.deinit();
    }
    const allocator = gpa.allocator();

    var a: *i32 = undefined;
    {
        var b = try allocator.create(i32);
        b.* = 10;
        a = b;
        std.debug.print(
"address of b is: {s}\n", .{&b});
    }
    std.debug.print(
"a is pointing to: {s}\n", .{a});
    const c: i32 = a.*;
    std.debug.print(
"{d}\n", .{c});
    a.* = 11;
    std.debug.print(
"{d}\n", .{a.*});
}


Rust程式碼:

fn main() {
    let a:i32;
    let a_ptr = &a;
}

上述程式碼無法編譯。這是因為let c = *a_ptr。在 Rust 中,取消引用原始指標被視為不安全的行為,程式設計師應該unsafe在必要時將該程式碼隔離到一個塊中。與 C++ 或 Zig 相比,Rust 使這些基本場景本質上更安全。

更多點選這裡

總結
本文並非對各個語言的記憶體管理模型進行全面介紹。我想介紹的是每種語言中記憶體安全工作原理的公平和基本觀點。

C++ 語言哲學允許你實現最廣泛的行為,並且確實擁有所有花哨的功能。程式設計師可以自由選擇自己的風格,並且必須根據自己的意願實現某些功能,這可能會提供最佳的記憶體安全模型或最差的記憶體安全模型,具體取決於程式設計師和專案。

Zig 在 C++ 的自由度和 Rust 僵硬的記憶體管理模型之間實現了平衡,因此具有相當強的記憶體安全性,並且編寫程式碼非常有趣。學習這門語言也相對簡單,學習曲線更平緩,提供直觀的功能,同時輕輕地推動編寫記憶體安全的程式碼。

Rust 確實提供了強大的記憶體安全保證,因此編譯器非常嚴格,控制力很強。除了基礎知識之外,當你進入生命週期和非同步程式設計時,事情就會變得非常困難,語言也會成為一個難以攻克的難題。
 

相關文章