【超重要】Linuxカーネルが認めた!信頼性を高めるC言語の「型」の書き方
はじめに
どうも私立YouTube高専校長です。
本日は、C言語のプログラムからバグを減らす、「型」の書き方について、お話をしたいと思います。 C言語は、50年近く歴史のある言語ですが、現在もなおバリバリ現役の言語で、下回りのプログラムを書くためによく使われています。 したがって、ライブラリなどを辿っていくと、C言語に行き当たる場合も多いので、普段はC言語を書かない方も、本日の内容は参考になると思います。
この動画をご覧の方の中で、高専や大学でプログラムの授業を受けたことのある方は、C言語に触れたことがある人も多いかもしれません。 そして、もしかしたら、整数の変数の型として、char, int, short, longを使うよう教わったのではないでしょうか? そのような方は、この動画のサムネイルを見て驚いたかもしれません。
実は、これらの型は、本や授業では、良く紹介されるのですが、C言語の古い書き方で、使い方によっては、プログラムでバグが発生する可能性があるので、基本的には使用してはいけないという、少し衝撃的なお話をしたいと思います。 私自身も、今日ご紹介する型を知る前は、学校や本で教わったこれらの型を涼しい顔で、平気で使っていました。 しかし今思うと、すごく危険なコードを書いていたのですが、誰にも教わることがなかったので、今日はそれをシェアしたいと思います。
そもそも、皆さんが、C言語の変数を定義するときに、型を何にするか悩んだ経験はないでしょうか? 私も似たような経験があります。私の場合は、C言語を学びたてのころは、型をどうするか悩みさえせずに、適当に型を決めていたことさえあります。 しかし、C言語では、コンパイラやプロセッサーの振る舞いを意識して、適切な型を選ばないと、オーバーフローに起因する様々なバグが発生する可能性があります。 皆さんも、C言語でバグのない、安全なプログラムを書けるようになるためには、変数を定義するときに、 適切な型を自分で選択できるようになる必要があります。 そこで、本日は、これらの型を使うことの危険性と、その代わりとして使える安全な型をご紹介したいと思います。
実際に、C言語で3000万行も書かれているLinuxカーネルでも、多くのソースコードで、今回ご紹介する型が使われていて、安全な型として支持する声が多いです。
また今回紹介する情報以外にも、こういった有益なコンピュータ関連の技術情報を発信しているので、 そういった動画を見逃したくない方は、今のうちにチャンネル登録しておいて貰えればと思います。 このチャンネルでは、私が、高専・東大・スタートアップ・日系大手メーカー・外資ハイテク企業、、と人生の大半をコンピュータに投下して、学んできた全てをぶつけます。
それでは、本編どうぞ
結論
いきなり、結論から言うと、
以下のように置き換えてください。 左の型は古いC言語の型で、右の型は1999年にC99という規格で定義された固定幅整数型になります。C99の267ページの"Exact-width integer types"という項目です。 よっぽど古い環境で開発しない限り、固定幅整数型が使えるので、基本的に、左の型はもう使用しないでください。
理由は、2つあります。 1つはソフトウェアの信頼性、もう1つは可読性です。 いきなり理由を説明しても理解できないと思うので、まずは、そもそも型システムというのがどういう仕組みなのかについて説明しようと思います。
型システム
C言語で変数を定義するときに、そもそも、何のために型を指定しているのかご存知でしょうか? 何も考えずに書いていると、ただただ、めんどくさいだけですが、意味を理解して書くと、実は、非常に大きな恩恵を受けることが出来ます。
型というのは、コンパイラに変数の意味を教えてあげるためのもので、コンパイラは変数の型によって、メモリに配置される変数のサイズやプロセッサーが処理するための命令を変えます。 例えば、32bitの整数と32bitの浮動小数点数の足し算では、メモリ上では同じ32bitのビット列でも、それぞれデータの表現方法(意味)が違うので、プロセッサーもそれぞれ異なる命令を使わないと、正しい計算ができません。なので、コンパイラに変数の型を教える必要があります。
実際に、gccを使ってコンパイルして、intとfloatに対する命令の違いを見てみましょう。Windowsでのgccの環境構築方法は過去に投稿した動画があるので、そちらを参考にしてください。
~実習終了~ これが、コンパイラに型を教える意味の1つになります。 もう1つ、コンパイラに変数の型を教える意味があるので、そちらについてもご紹介したいと思います。
コンパイラは変数の型を照らし合わせて、コンパイル時にバグを検出してくれます。
例えば、間違えて、32bitの整数に、32bitの浮動小数点数の値を代入するプログラムを誤って書いたとします。浮動小数点数の値は、非常に大きくなる場合がありますが、32bitの整数では2^16-1までしか、表現できないので、オーバーフローで、プログラムが意図通りに動かない可能性があります。しかし、コンパイラは、代入する変数の型と代入される変数の型を照らし合わすことで、それを検出して、警告を出してくれます。
実際に、gccを使ってコンパイルして、ワーニングを見てみましょう。
~実習終了~
このように、コンパイル時にバグを検出してくれるのも、C言語において、プログラムで型を指定する意味になります。 逆に、Pythonのように動的に型が決まる言語だと、実行時に初めてエラーが出ることがあるので、比較として すこし説明したいと思います。
~実習終了~
これでは、実行するまで、バグが分からないため、Pythonのプログラムでサービスを公開した場合、サービスの本番実行時に初めてバグが出るような事もあり得てしまいます。 そう考えると、C言語はプログラムで型を指定する事で、バグを実行前に検出できるというメリットが伝わったんじゃないかと思います。
以上での説明で、そもそもなぜ型を指定しているのか、分かったと思うので、本日の本題の古い型を使用してはいけない理由について、次は、説明したいと思います。
理由
まずは、ソフトウェアの信頼性です。
先ほど、変数の型によって、メモリに配置される変数のサイズが異なるというお話をしましたが、実は、C言語の仕様で、char以外の整数型は、サイズが未定義で、プロセッサーに依存しています。例えば、符号なしintだと、一般的に皆さんが使用するようなPCの環境(x86-64)では、32bitになります。しかし、Arduinoでintを使うと、実は、16bitしかありません。変数のサイズが違うと、当然値の範囲が違うので、一方では正しく計算できるアルゴリズムが、他方ではオーバーフローして、意図通りに動かないということがあり得ます。 結果として、intのような古い整数型を使うと、ソースコードの移植性が下がる上に、そもそもこの違いを知らないプログラマーが変数のサイズを勘違いして、バグが生まれる可能性があります。
実際に、x86-64とArduinoのAVRマイコン向けにそれぞれコンパイルを行って、変数のサイズを確認してみましょう。 おそらく、皆さんが普段お使いのパソコンも同じx86-64というアーキテクチャだと思います。これについては、別動画で詳しく説明しているので、詳しくはそちらをご覧下さい。
~実習終了~
これで、確かにプロセッサーによってintのサイズが変わってしまうというのが分かったと思います。
また、リッチな環境でも、longだと、4バイトのものと8バイトの物があります。このような表を暗記して、アプリケーションのプログラムで違いを吸収しようとするのは、愚かな考え方です。したがって、先ほどご紹介したように、C99で定義された固定幅整数型を使えば、プロセッサーによる変数のサイズの違いを気にかける必要が無くなり、ソフトウェアの信頼性を上げることが出来るのです。
次に、2つめの理由を説明したいと思います。 それは、ソフトウェアの可読性です。これは、専門的な知識がなくてもわかりやすいと思います。 古い型を使用する限り、頭の中に常に、変数のサイズのプロセッサー依存を意識する必要があります。これは、以前の動画でご紹介した認知負荷が高まるというものに当てはまります。認知負荷が高いコードは、可読性が低いです。ソースコードを読む人が、プロセッサーごとの変数のサイズの違いを暗記していることを期待するのは、現実的ではありません。
一方で、C99の固定幅整数型を使えば、変数のサイズがプロセッサーに依存せず、一目で分かるので、プロセッサーの違いを意識する必要がなくなります。例えば、uint32_tの変数が32ビットであることは、C言語を始めたばかりの初心者でも分かると思います。
このように、C言語でchar, short, int, longのような古い型を使用すると、サイズの違いに起因するバグが生まれる可能性が上がり、可読性も落ちるので、C99で固定幅整数型が定義されました。しかし、残念ながら、C言語は非常に歴史のある言語なので、昔の仕様を知っている人が、知識をアップデートしないまま授業をしたり、本を書いたりするので、いまだに古い型が多くの学習者に教えられてしまうというのが現状になります。最後にまとめを行いたいと思います。
まとめ
本日のまとめです。 もしかしたら、C言語を勉強中の方は、今日の話を聞いて、こう思ったかもしれません。 「C言語の型を決めるだけでも、コンパイラやプロセッサーの振る舞いを意識しなければならないのか。難しすぎるよ。。」と。
たしかに、C言語は難しいです。しかし、C言語を使いこなすことは、コンピュータを使いこなすことと、殆ど同義です。 C言語を使いこなせるようになれば、コンピュータを手足のように扱い、最高のパフォーマンスを引き出す事が出来るようになります。
**その分学習コストも高くなりますが、安心してください。**私のチャンネルでは、C言語を書くために必要な、コンピュータ関連の技術を原理から分かりやすく解説します。 この動画を見ているあなたは、C言語を使いこなせるようになるかと、不安になる必要はまったくありません。
なぜかというと、こんな型だけの話を、30分も聞ける、あなたはポテンシャルの塊なんです。 自信を持って好奇心のままに勉強してください。自信を無くして、投げ出してしまう人が殆どです。 5年後には、天地の差が開いています。
なにか分からないことがあったら、ぜひコメントで聞いてください。
自分もまだまだ知らないことはあるので、一緒に勉強していきましょう。 私が高専・東大・スタートアップ・日系大手メーカー・外資ハイテク企業と、、、人生の大半をコンピュータに投下して、学んできた全てをこのチャンネルにぶつけるので、私の動画を見ることによって、他では得られないコンピュータの原理的な考え方が身に付きます。
絶対にできます!一緒に頑張りましょう! 最後に復習をして、本日の動画を終わりにしたいと思います。
復習
本日は、char, int, short, longは、本や授業では、良く紹介されるのですが、C言語の古い書き方で、プログラムでバグが発生する可能性があるので、基本的には使用してはいけないという、少し衝撃的なお話をしました。 理由としては、変数のサイズが、C言語の仕様で定義されていないため、プロセッサーによっては、オーバーフローなどの不具合につながる可能性があること、さらに、ぱっと見で変数のサイズが分からないため、可読性が低いことをお伝えしました。
この動画を見た方は、明日からはC99の固定幅整数型を使用して、信頼性・可読性の高いプログラムを書いて頂けたらと思います。
本日の動画は以上となります。ここまでご視聴ありがとうございました。
Reference
CHAPTER11 Data Types in the Kernel
Fixed width integer types (since C99)