# 卒研経過報告 #8 Rustによる組み込みプログラミング マイコンWio terminalを用いたRustとC言語の信頼性,安全性の比較検証、考察 [先週のレジュメ](https://hackmd.io/@Fteam/ryeBrX3rt) ## 研究の目的 この研究ではこれまで主に組込みソフトウエア開発に使われてきたC/C++と それに替わる有効な選択肢として、注目されているRust言語との 信頼性や安全性など様々な面から比較検証を行う。 ## memo C言語だと危険な共有書き込みの具体例とrustによる解決例 ## 検証 #### 1.コピー先とコピー元が重なった時のエラー strcpy()の停止条件'\0'がコピーによって消えるため、無限ループに陥り,不正なアドレスへの書き込みを行う. :::spoiler コード ```c= #include<stdio.h> void strcpy(char *dst, char *src){ int i = 0; do{ dst[i] = src[i]; if(src[i] == '\0') break; i++; }while(1); } int main(void){ char str[] = "Hello!"; strcpy(str +1, str); printf("%s\n", str); return 0; } ``` ``` Segmentation fault ``` ```rust= fn main(){ //let str1: &str = "Hello!"; //let str2 = String::from("Alice"); //let str2 = str1.clone(); //println!("str1:{},{:p}", str1, &str1); //println!("str2:{},{:p}", str2, &str2); let mut msg = String::from("My name is Koyasu."); //let slice1 = &msg[..7]; //let slice2 = &msg[11..msg.len()]; let msg2 = &msg[8..msg.len()]; msg = msg2.to_string(); //スライスが参照するデータの代入 println!("msg:{}", msg); } ``` **疑問** - rustの文字列はcharの配列としてメモリに格納されているわけではない - Rustの文字列は、添え字アクセスをサポートしていない ```rust=2 let s1 = String::from("hello"); let h = s1[0]; //error ``` - スライスではrustで重複コピーした際にcの例のように一部を重複させることはできず参照するデータの代入しかできないのではないか - 生ポインタはrustの所有権システムで実現できないような柔軟な構造を作ることができるが,unsafe()ブロックを用いることが必須であり,このブロック内で記述したコードはプログラマの責任となるためrustによる安全性は保障されないのではないか ::: #### 2.バッファオーバーフロー 符号付き整数のオーバーフローはc言語では未定義 int型の変数に保持できる最大値を超える値を格納しようとしたときに発生する。 :::spoiler コード ```c= #include <stdio.h> #include <stdlib.h> int main(void){ int x=1; for(int i=0;i<10;++i)x*=100; printf("%d\n",x); } ``` ``` 1661992960 ``` 特にエラーが出ることなく実行できるが、値はめちゃくちゃ ```rust= fn main(){ let x:i8=127; let b:i8=100; let c:i8=x+b; println!("{}",c); } ``` rustでは8bitの符号付き整数型の最大値127を超えると ``` error: this arithmetic operation will overflow --> src\main.rs:4:14 | 4 | let c:i8=x+b; | ^^^ attempt to compute `i8::MAX + 100_i8`, which would overflow ``` コンパイルすると 「この算術演算はあふれています」というエラーが出る ::: #### 3.バッファオーバーフロー(追記) c言語における危険な関数`gets()` ,`printf()` :::spoiler コード gets()の例 ```c= #include<stdio.h> int main(void){ char s[5]; gets(s); printf("%s",s); } ``` scanf()の例 ```c=4 char s[5]; scanf("%s", s); ``` ``` aaaaaaaa *** stack smashing detected ***: <unknown> terminated 中止 ``` これらの関数は変数のサイズを間違えるとバッファオーバーフローが発生してしまい,改善策はプログラマが気を付けてコーディングする他ない.rustでは2.のようにコンパイラが事前に警告を出力する為,絶対的に安全なコードを記述することができる. ::: #### 4. 未初期化メモリの使用 値を初期化せずにprintする. :::spoiler コード ```c= #include<stdio.h> int main(void){ int a; printf("%d\n", a); } ``` ``` 0 ``` ```rust= fn main() { let a; println!("{}", a); } ``` ``` Compiling playground v0.0.1 (/playground) error[E0282]: type annotations needed --> src/main.rs:2:9 | 2 | let a; | ^ consider giving `a` a type For more information about this error, try `rustc --explain E0282`. error: could not compile `playground` due to previous error ``` ::: c言語では文法的な誤りがないため,宣言した'a'を初期化せずとも実行できるが,rustでは初期化してない値を出力しようとした場合コンパイラが止めてくれる.初期化していない変数が"0"である保証はなく,これに気付かぬまま実行することはとても危険といえる. #### 5. [前回]無限再帰 前回発表したrustで記述した無限再帰のコード,コンパイラが止めてくれるようだ :::spoiler エラーコード ``` warning: function cannot return without recursing --> loop.rs:1:1 | 1 | fn loop_1(){ | ^^^^^^^^^^^ cannot return without recursing 2 | loop_1(); | -------- recursive call site | = note: `#[warn(unconditional_recursion)]` on by default = help: a `loop` may express intention better if this is on purpose warning: 1 warning emitted ``` 警告:関数は再帰せずに戻ることはできない ::: [](https://codezine.jp/article/detail/4700) ## 参考文献 [1] rustプログラミング入門 オーム社 [2] プログラミングrust オライリー・ジャパン [公式ドキュメント](https://doc.rust-jp.rs/book-ja/ch08-02-strings.html) [](https://teratail.com/questions/56792) [](https://prev.rust-lang.org/ja-JP/faq.html) [](https://marycore.jp/prog/c-lang/dangerous-gets-safely-gets/) [](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%83%B3) [](https://ja.wikipedia.org/wiki/Scanf#%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%83%B3) :::spoiler メモ ```rust= use std::io; fn main() { println!("What's your name?"); let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); println!("Hello {}!", input); } ``` #### 2.配列外の値へ不正アクセス 配列番号の外の値を指定し,メモリ破壊を起こすエラー ```c= #include<stdio.h> int main(void){ int a[3] = {0, 1, 2}; int b[3] = {3, 4, 5}; a[3] = 99; // bug for(int i = 0; i < 3; i++) printf("a[%d] = %d\n", i, a[i]); for(int i = 0; i < 3; i++) printf("b[%d] = %d\n", i, b[i]); return 0; } ``` 内容が無理やりすぎる ありえそうな例を出してみるといい. :::