追記: Twitterなどでご指摘等いただいているのをコメントしてますので、ぜひそちらもご覧ください!

これはなに

絶賛Rust勉強中の僕が、「なんでRustはメモリ安全って言われているの?」と聞かれたので、実際にメモリ安全ではない(?)C言語のコードを並べてみて考える記事です。

C言語の場合

メモリリーク

以下のコードはメモリリークを起こす可能性があります。

#include <stdio.h>
#include <stdlib.h>

#define STR_BUF_SIZE 100000000

char *str_new() { return calloc(STR_BUF_SIZE, sizeof(char)); }

int main() {
  for (int i = 0; i < 100000000; i++) {
    char *str = str_new();
    snprintf(str, STR_BUF_SIZE, "[number: %d]", i);

    printf("%s\n", str);
  }

  return 0;
}

メモリリークを起こす原因としては、callocによってアロケートされたメモリが不要になっているにも関わらず、開放されないためです。

バッファオーバーラン

以下のコードは、確保したメモリの領域外にアクセスがされてしまう例です。

#include <stdio.h>

#define BUF_SIZE 16

int main() {
  int buf[BUF_SIZE];

  for (int i = 0; i < BUF_SIZE + 2; i++) {
    buf[i] = i;
    printf("buf[%d]: %d\n", i, buf[i]);
  }

  return 0;
}

配列bufのサイズ16を超えて書き込みが行われてしまっており、また、エラーにならずに実行できてしまいます。

Rustの場合

メモリリーク

Rustには所有権・ライフタイムの仕組みがあり、スコープをでると自動的に開放(drop)されます。

fn main() {
    for i in 1..10000000 {
        let str = format!("[number: {}]", i);
        println!("{}", str);
    }
}

そのため、C言語の例のように、動的に確保したメモリを開放し忘れる心配がいらなくなります。

また、ライフタイムの仕組みによって、ダングリングポインタなども考える必要がなくなります。

バッファオーバーラン

Rustでは、実行時に確保されたメモリ領域内であるかのチェックが走るようになります。

const BUF_SIZE: usize = 16;

fn main() {
    let mut buf = [0; BUF_SIZE];

    for i in 0..(BUF_SIZE + 2) {
        buf[i] = i;
        println!("buf[{}]: {}", i, buf[i]);
    }
}

なお、実行時チェックのオーバーヘッドがあるようなので、イテレータなどを使うようにするのが良い様です。

さいごに

他にもあると思いますが、これでRustのメモリ安全のイメージが湧くと嬉しいです。
間違いなどありましたら、コメント・編集リクエストから教えていただけると勉強になります。

Refs

    • 安全と危険のご紹介 – Rust裏本

 

    • なぜRustなの?と言われた時のために

 

    • Rustのメモリ管理機能とその特徴 | 己の不学を恥じる

 

    • Rustのメモリ管理って面白い – Qiita

 

    コンセプトから理解するRust:書籍案内|技術評論社
广告
将在 10 秒后关闭
bannerAds