C -安全!


導入


Cはメモリセーフな言語ではなく、それは絶対に正しいです.バッファオーバーのすべての場所、メモリリーク、およびSegDisxもタブーになっていた.この小さな記事では、いくつかの安全なCコードを表示するつもりです、我々は錆でそれを書き直すつもりです.

問題


次のコードを考えます.
// lifetimetest.c

#include <stdio.h>
#include <string.h>

char*
longest(char* a, char* b)
{
    if(strlen(a) > strlen(b)){
        return a;
    }
    return b;
}

main(void)
{
    char a[] = "Hello";
    char b[] = "KosherFoods";
    char* res;

    res = longest(a, b);
    printf("%s", res);
}

ここで関数を定義しますlongest これは2文字ポインタ(本質的に文字列)をとり、より長い文字列を返す.
このプログラムをコンパイルして実行するならば$ gcc lifetimetest.c -o lft.out && ./lft.out
この出力を取得します.KosherFoodsどちらが期待通りだ
さあ、このプログラムを少し書きましょう
#include <stdio.h>
#include <string.h>

char*
longest(char* a, char* b)
{
    if(strlen(a) > strlen(b)){
        return a;
    }
    return b;
}

main(void)
{
    char a[] = "Hello";
    char* res;
    {
        char b[] = "KosherFoods";
        res = longest(a, b);
    }

    printf("%s", res);
}
ここで移動b 別のスコープに
このプログラムをコンパイルして実行するならば$ gcc lifetimetest.c -o lft.out && ./lft.out
この出力を取得します.KosherFoodsこれまで何も変わったことはない.
もう一度プログラムを書き換えましょう!
#include <stdio.h>
#include <string.h>

char*
longest(char* a, char* b)
{
    if(strlen(a) > strlen(b)){
        return a;
    }
    return b;
}

main(void)
{
    char a[] = "Hello";
    char* res;
    {
        char b[] = "KosherFoods";
        res = longest(a, b);
    }
    char ohnoo[] = "Plan 9 from User Space";
    printf("%s", res);
}
ここでは、2番目のスコープの後に新しい文字列変数を宣言しました
このプログラムをコンパイルして実行するならば$ gcc lifetimetest.c -o lft.out && ./lft.out
この出力を取得します.Plan 9 from User Spaceでも……どうやって起こるの?

解説


Cでは、文字列は本質的に文字配列であり、それらの文字列を「保存する」変数は配列の先頭へのポインタだけです.
So longest 文字列のコピーを返しませんが、メモリ内の文字列の先頭へのポインタです.これで心を続けましょう.変数宣言b 内部スコープでは、スコープのメモリに入れられますが、スコープが終了すると、他の変数がメモリに置かれますb かつては.so res まだ言いましょう0x000004 どこb 昔は,今は0x000004ohnoo 文字列.

錆を書き直す


実行に移る前に、いくつかのことを話し合いましょう.さびには「一生」という言葉があります.リファレンスの寿命と、それは参照している参照を妨げます.心でそれを続けましょう.
まず、最長の関数を実装しましょう.
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() {
        a
    } else {
        b
    }
}
ヒア'a は寿命指定子で、これらのパラメータは少なくとも'a , これで心を続けましょう.
次のコードを考えます.
fn main() {
    let a = String::from("APCHIHBALONGERSTRING");
    let result: &str;
    {
        let b = String::from("Banana");
        result = longest(a.as_str(), b.as_str());
        println!("Longest: {}", result);
    }
}
を返します.$ cargo run次の出力を取得します.
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/lifetime_test`
Longest: APCHIHBALONGERSTRING
あなたがそうすることができるように、さびコンパイラは文句を言いませんでした.
それでは、より高いライフタイムでより低いライフタイムBorrowを使用してみましょう.
fn main() {
    let a = String::from("APCHIHBALONGERSTRING");
    let result: &str;
    {
        let b = String::from("Banana");
        result = longest(a.as_str(), b.as_str());
    }
    println!("Longest: {}", result);
}
を返します.cargo run次の出力を取得します.
Compiling lifetime_test v0.1.0 (/home/ernest/projects/lifetime_test)
error[E0597]: `b` does not live long enough
  --> src/main.rs:14:38
   |
14 |         result = longest(a.as_str(), b.as_str());
   |                                      ^^^^^^^^^^ borrowed value does not live long enough
15 |     }
   |     - `b` dropped here while still borrowed
16 |     println!("Longest: {}", result);
   |                             ------ borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `lifetime_test` due to previous error
これは私たちがリファレンスを借りるからですb , しかし、後に我々はそれを使用してresult 外側のスコープ(より高い生存期間)で無効です.
結果として、Cで明示的に使用していない限り、Cで同じように未定義の動作を得ることができませんunsafe キーワード.

結論


この記事では、なぜCが危険でトリッキーに使用されるかの例を示しました.我々は、同じ錆コードの例を提供し、どのように錆はこのような問題を扱う示した.

ノート


私は錆が好きです、そして、私はそれが低レベルのもののようなそれが素晴らしいと思います:オーディオライブラリ、テレビゲームエンジン、ウェブサーバ、その他

Further Reading

  • https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
  • https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
  • https://www.rust-lang.org/