CのあいまいなソースコードをRustで実行する
この記事はCの未定義動作など、あいまいなソースコードをRustでコンパイル・実行しコンパイル・実行結果を比較してみよう、というものです。
Webブラウザのみで確認できるシミュレータを使うので、気になった方は後述のシミュレータで動作確認してみてください。
背景
Rustの学習を始めたので自社の勉強会でアウトプットすることにしました。
C/C++とRustのコードを比較し、Rustの安全さ・モダンさを知ってもらえれば・・・と思いました。
RustからC/C++の安全なソースコードの書き方も学べるかな、と思ったのも勉強会・記事を書いた理由です。
C/C++, Rust初心者の方向けの内容と思います。
資料とソースコード
資料とソースコードは次の場所に置いています。適宜参照ください。
資料
※資料の28ページ以降にはCとRustの実行結果を記載しています。
こちらは後から確認することをお勧めします。
ソースコード
未定義動作
RustとCのコードを比較するにあたりCの標準規格はC99にすることにしました。
理由は仕事で使っているコンパイラのバージョンと同じだからです。
C99の未定義動作の定義はこちらです。
未定義動作
コンパイラは
未定義の動作に対して,その状況を無視して予測不可能な結果を返してもよい。
だそうです。
未定義動作に該当するコードを書いてしまうと危険そうですね・・・。
確認環境
RustとC/C++の確認環境は次にしました。
C/C++
wandbox
Cの設定はデフォルトから次の項目を変更します。
- Languages ⇛ C
- コンパイラ ⇛ gcc 11.1.0
- 標準規格 ⇛ C99
Rust
Rust Playground
デフォルトの設定のままにします。動作確認したときの設定は次になっていました。
- Stable version: 1.54
- Edition 2018
確認テーマ(CとRustの比較)
次のテーマについてC、Rustでコンパイル・実行結果を比較します。
ファイル拡張子はCは*.c, Rustは*.rsです。
1. 整数型オーバーフロー: val_overflow.*
整数型の有効範囲を超えた値を代入し実行してみます。
2. 未初期化変数の参照: uninitialized_val.*
定義した変数を初期化することなく参照・使用します。
3. ゼロ割り: div_by_zero.*
0で割ります。
4. 配列要素外アクセス: array_index_err.*
定義した配列の要素数を超えた領域にアクセスします。
5. ビットシフトオーバーフロー: shift_overflow.*
変数型のビット長を超えてビットシフトさせてみます。
6. ビットシフト負の数: shift_negative.*
ビットシフトに負の数を指定します。
7. enum, switch: enum_switch.*
switchとmatch、enumについてCとRustで比較してみます。
- Cはswitch、Rustはmatch文の条件をひとつ削除して実行してみてください。
- print_shape(Circle);の引数Circleを1(または任意の整数値)に変更して実行してみてください。
確認テーマ(C++とRustの比較)
ファイル拡張子はC++は*.cpp, Rustは*.rsです。
wandboxの【Languages】は【C++】に変更し実行してみてください。
1. 多態: polymorphism.*
多態の説明にありそうなサンプルコードを書きました。こちらのテーマはソースコードを比較してみてください。
次がサンプルコードのクラス図です。
図形を抽象クラスとして、円・三角形・正方形の各クラスに実装を行います。
抽象クラスのメソッドは次の2つです。
- 図形の種別を表示するprint_type
- 図形の面積を計算するcalc_area
実装クラスに面積を計算するためのプロパティを追加しています。
私は多態をRustで実現したことがなかったので実装してみました。
コードの比較をするためにC++でも多態を書いてみました。
普段C++を使っていないこともあり仮想関数の実装について忘れていました。
Rustで多態をやってみた感想ですが、C++と変わらずむしろC++よりもシンプルに書けたような気がしました。
最後に
ここまで読んでいただきありがとうございました。
C・C++とRustの比較は如何でしたでしょうか?
実際にソースコードを実行することでRustのコンパイラの安全性が体感できたのではないでしょうか。
Rustの言語仕様からより安全なC、C++ソースコードを書く際のなにかヒントになれば嬉しいです。
Discussion