追記: 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のメモリ安全のイメージが湧くと嬉しいです。
間違いなどありましたら、コメント・編集リクエストから教えていただけると勉強になります。