型の安全性
出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2022/05/30 09:44 UTC 版)
型にまつわるものに限らず、プログラムの安全性(safety)とは、プログラミング言語や文脈によって定義が異なる場合があり、一概に述べることはできない。ひとつの指標として、プログラムが言語仕様で定義されていない「未定義」の状態に陥らない、という性質のことを指すことがある。たとえばC言語やC++の標準では、NULLポインタのデリファレンスや、配列の範囲外アクセスによるバッファオーバーランなど、そういった「未定義」の動作を引き起こすケースが決められている。大抵の実行環境では、NULLポインタのデリファレンスによってセグメンテーション違反(アクセス違反)が引き起こされ、オペレーティングシステムによってプログラムが異常終了させられることになるが、必ずしもそうなるとは限らず、実際には何が起こるか分からない。「安全な」プログラムを記述するためには、言語未定義の動作を避けるように注意深くコーディングしなければならない。この指標の観点では、プログラムのエラーを、言語未定義や処理系依存の異常動作によってではなく、ランタイムやインタプリタが検出して仕様通りに異常終了するような場合は「安全」の側に含まれることになる。一般的に仮想マシン上で動作するJavaやC#のような言語は、ランタイムによって検証され、信頼されたコードのみを実行する仕組みが用意されているため、C/C++よりも安全である。未定義動作はコンピュータセキュリティと密接な関係があり、例えばバッファオーバーランが引き起こされると悪意のある不正なコードを実行できてしまったりするセキュリティホールにつながることがある。ただし安全なチェック機構のある言語ほどオーバーヘッドが大きくなるため、安全性は実行速度とのトレードオフの関係にある。 例えば、JavaではNullオブジェクトを参照した場合、実行時にNullPointerException例外がスローされると言語仕様で規定されており、この点でC言語やC++よりも安全であると言える。しかし、JavaではNullオブジェクトを参照するコードを記述しても言語構文上は合法とみなされるため、コンパイラによるチェック機構は働かず、実行時にNullPointerExceptionがスローされるまでプログラミングミスに気づかない可能性がある。さらに進んだKotlinのように「Null安全」な言語では、Nullの状態を許容しない型を定義することができ、このNull非許容型を使う限りはNullオブジェクトを参照することはないことが保証される。また、Kotlinでは主にJavaとの相互運用のためにNullの状態を許容する型も定義できるが、このNull許容型を使う場合は、事前にNullチェックコードを記述することを言語仕様によって強制されるため、安全性が増す。 そして型にまつわる安全性のことを型安全性(英: type safety)と呼ぶが、一般的に型安全性とは、データ(オブジェクト)の本来の型に従ってプログラムを正しく実行できる性質のことを指す。前述のように型安全性が具体的にどのようなものであるかはプログラミング言語や文脈に依存する。 一般的にコンピュータでは整数と浮動小数点数のように異なる型の値同士の演算はできず、いったん同じ型に揃えて演算する必要がある。プログラマがコード上で型変換(キャスト)を明示する必要のある厳しい言語もあれば、コンパイラによってある程度の暗黙の型変換や型昇格がなされるようなゆるい言語もある。さらにCにおいては、キャスト(型変換)構文を使うと、互換性のない型同士でも強制変換できてしまう。例えば整数とポインタとの間で相互変換することや、整数へのポインタを浮動小数点数へのポインタに無理やり変換することもできてしまい、簡単に型安全性が破壊される。誤った型へのポインタ経由で領域にアクセスした場合の動作は未定義である。型検査によって型安全性を確保するために、互換性のない型同士の変換は明示的・暗黙的問わず一切できないようになっている言語や、間違った型変換をすると実行時に例外をスローする言語もある。 型安全でない例としては、C/C++において、可変長引数の関数を利用する場面が挙げられる。代表的なものがprintfやscanfで、これらの関数は任意の数のあらゆる型のデータ(オブジェクト)を可変長引数として統一的に渡せるように、いったん引数オブジェクトの型情報を消去し、別途入力として与えられた書式文字列をもとに型情報を関数内部で復元する仕組みになっている。しかし書式文字列の指定にミスがあると、本来のオブジェクトの型とは異なる誤った型として取り出すことになってしまうが、型が消去されてしまっているためにコンパイラはミスを検出することができない。結果として、想定されたものとは異なるでたらめな値が出力されてしまったり、間違った型へのポインタを経由することで誤った領域にデータを書き込んでしまいクラッシュしたり、といった未定義の異常動作を引き起こす。一方、JavaやC#の可変長引数では、実引数として渡される個々のオブジェクト自身が型情報を保持しており、書式指定ミスによる型の不一致があった場合は例外をスローして安全にプログラムを中断・異常終了させることができるため、C/C++よりも型安全性が高い。 別の例として、ジェネリックプログラミング(ジェネリクス)をサポートしていなかった初期のJavaやC#では、可変長の動的配列や双方向リストなどのコレクションに型Tのオブジェクトを格納する場合はいったん基底型であるjava.lang.ObjectやSystem.Objectにアップキャストし、またコレクションからオブジェクトを取り出す場合は型Tにダウンキャストしなければならず、正しい型のオブジェクトとして取り出すためには注意深くコーディングする必要があった。ジェネリクスをサポートすることで、コレクションに格納する型Tを静的に決めることができるようになり、誤ったキャストにより実行時エラーを引き起こす可能性を排除できるようになった。このような改善も型安全性の向上とみなされる。
※この「型の安全性」の解説は、「型システム」の解説の一部です。
「型の安全性」を含む「型システム」の記事については、「型システム」の概要を参照ください。
- 型の安全性のページへのリンク