安全なプログラミング言語(PL)が人気を集めています。安全なPLの正式な定義は何でしょうか。たとえば、Cは安全ではありませんが、Javaは安全です。PL自体ではなく、PL実装にプロパティ「safe」を適用する必要があると思います。もしそうなら、安全なPL実装の定義について議論しましょう。この概念を正式にしようとする私自身の試みは奇妙な結果をもたらしたので、他の意見を聞きたいと思います。すべてのPLに安全でないコマンドがあると言ってはいけません。安全なサブセットをいつでも取得できます。
安全なプログラミング言語(PL)が人気を集めています。安全なPLの正式な定義は何でしょうか。たとえば、Cは安全ではありませんが、Javaは安全です。PL自体ではなく、PL実装にプロパティ「safe」を適用する必要があると思います。もしそうなら、安全なPL実装の定義について議論しましょう。この概念を正式にしようとする私自身の試みは奇妙な結果をもたらしたので、他の意見を聞きたいと思います。すべてのPLに安全でないコマンドがあると言ってはいけません。安全なサブセットをいつでも取得できます。
回答:
何らかの点で言語を「安全」と呼ぶとき、それは正式には、その言語の整形式プログラムが危険と考える何かをすることができないという証拠があることを意味します。「安全」という言葉もあまり正式には使用されませんが、それはここの人々があなたの質問の意味を理解していることです。「安全な」言語に持たせたいプロパティには、さまざまな定義があります。
いくつかの重要なものは次のとおりです。
Andrew WrightとMatthias Felleisenの「型の健全性」の定義は、「 Wikipediaを含む」多くの場所で「型の安全性」の受け入れられた定義として引用されています。
Michael Hicks は、「メモリの安全性」のいくつかの定義をここにリストしています。発生できないエラーの種類のリストもあれば、ポインターを機能として扱うことに基づいているものもあります。Java unsafe
は、ガベージコレクターにすべての割り当てと割り当て解除を管理させることにより、これらのエラーが発生しないことを保証します(明示的にマークされた機能を使用しない限り)。Rustは、unsafe
アフィン型システムを使用して(ここでも明示的にコードをマークしない限り)同じ保証を行います。
同様に、スレッドセーフコードは通常、データレースやデッドロックなど、スレッドや共有メモリに関連する特定の種類のバグを示すことができないコードとして定義されます。これらのプロパティは多くの場合、言語レベルで適用されます:Rustは型システムでデータの競合が発生しないことを保証し、C ++ std::shared_ptr
は複数のスレッド内の同じオブジェクトへのスマートポインターがオブジェクトを途中で削除したり、最後の参照時に削除しないことを保証しますそれに加えて、CおよびC ++ atomic
には言語に追加の変数があり、適切に使用すると特定の種類のメモリ一貫性を強制するアトミック操作が保証されます。MPIはプロセス間通信を明示的なメッセージに制限し、OpenMPは異なるスレッドからの変数へのアクセスが安全であることを保証する構文を備えています。
メモリが決してリークしないプロパティは、多くの場合、スペースに対して安全と呼ばれます。自動ガベージコレクションは、これを保証する1つの言語機能です。
多くの言語では、その操作の結果が明確に定義され、プログラムが適切に動作することが保証されています。supercatが上記の例を示したように、Cは符号なし算術(安全にラップアラウンドすることを保証)に対してこれを行いますが、符号付き算術ではありません(オーバーフローは任意のバグを引き起こす可能性があります。オーバーフロー)、しかし、その後、言語は時々、無署名の量を署名されたものに静かに変換します。
関数型言語には多くの不変条件があります。たとえば、純粋な関数は副作用を引き起こすことができないなど、整形式プログラムが維持することが保証されています。これらは「安全」と表現される場合とされない場合があります。
SPARKやOCamlなどの一部の言語は、プログラムの正確性を証明するように設計されています。これは、バグから「安全」であると記述される場合とされない場合があります。
システムが正式なセキュリティモデルに違反できないことを証明します(そのため、「安全であると証明できるシステムはおそらくそうではありません」)。
「安全なプログラミング言語」の正式な定義はありません。それは非公式の概念です。むしろ、安全性を提供すると主張する言語は、通常、どのような安全性が主張/保証/提供されているかの正確な正式な声明を提供します。たとえば、言語は、型の安全性、メモリの安全性、または他の同様の保証を提供する場合があります。
double
またはlong
別のスレッドで変更されている間に、特定の方法で別の半分と混合された値の半分を生成しないことが保証されていない)、API仕様ただし、場合によっては未定義の動作があります。
導入部でBenjamin Pierceの型とプログラミング言語のコピーを手に入れることができれば、彼は「安全な言語」という用語に関するさまざまな観点の良い概要を持っています。
興味深いと思われる用語の解釈の1つは次のとおりです。
「安全な言語は、そのプログラマーのマニュアルによって完全に定義されています。」言語の定義は、言語内のすべてのプログラムの動作を予測するためにプログラマーが理解する必要があるもののセットです。Cのような言語のマニュアルは、特定のCコンパイラがメモリ内の構造をどのようにレイアウトするかの詳細を知らない限り、一部のプログラム(たとえば、未チェックの配列アクセスやポインター演算を含むプログラム)の動作を予測できないため、定義を構成しませんなど。また、同じプログラムを異なるコンパイラで実行すると、動作がまったく異なる場合があります。
そのため、「安全でない」という用語を使用してプログラミング言語の実装を指すのをためらいます。言語の未定義の用語が異なる実装で異なる動作を生成する場合、実装の1つがより期待される動作を生成する可能性がありますが、「安全」とは呼びません。
Safeはバイナリではなく、連続体です。
非公式の言い方をすれば、安全性とはバグに反対することを意味し、最もよく言及されている2つは次のとおりです。
これらは、言語が防止するバグの唯一のクラスではなく、データ競合の自由またはデッドロックの自由がむしろ望ましい、正確さの証明はかなり甘いなどです...
単純に間違ったプログラムはめったに(のみバギー)が「安全でない」とみなされていない、と長期安全性は、一般的に我々の能力に影響を与えるの保証のために予約されている理由プログラムについてを。したがって、未定義の動作を持つC、C ++、またはGoは安全ではありません。
そしてもちろん、開発者が言語保証を維持する責任があり、コンパイラが「ハンドオフ」モードにある領域を意図的に描写する安全でないサブセット(Java、Rust、...)を持つ言語があります。このエスケープハッチ、実際的な定義にもかかわらず、言語は一般にまだ安全と呼ばれています。
Obj.magic
Ocamlなど)。そして実際には、これらは本当に必要です
DWの答えに異論はありませんが、「安全」な部分の1つは対処されていないと思います。
前述のように、促進される安全性には複数のタイプがあります。なぜ複数の概念があるのかを理解するのは良いことだと思います。各概念は、プログラムが特に特定の種類のバグに苦しむという考えに関連付けられており、プログラマーは、言語がプログラマーのバグをブロックした場合、この特定の種類のバグを作成できないと考えています。
したがって、これらの異なる概念には異なるクラスのバグがあり、これらのクラスは相互に排他的ではなく、これらのクラスはすべての形式のバグをカバーしないことに注意してください。DWの2つの例を挙げると、特定のメモリロケーションが特定のオブジェクトを保持しているかどうかという質問は、型の安全性とメモリの安全性の両方の問題です。
「安全な言語」に対するさらなる批判は、危険と見なされる特定の構成要素を禁止することで、プログラマーが代替案を考え出す必要があるという観察から得られます。経験的に、安全性は優れたライブラリーによってより良く達成されます。すでにフィールドテスト済みのコードを使用すると、新しいバグを作成する手間が省けます。
CとJavaの基本的な違いは、Javaの特定の容易に識別可能な機能(Unsafe
名前空間など)を回避した場合、「誤った」アクションを含め、試行可能なすべてのアクションは限られた範囲の結果しか得られないことです。これにより、少なくともUnsafe
名前空間を使用せずにJava でできることは制限されますが、誤ったプログラム、またはより重要なことに、正しく処理されるプログラムによって引き起こされる可能性のある損害を制限することも可能になります有効なファイルですが、エラーのあるファイルから特に保護されていません。
従来、Cコンパイラは、「通常」の場合、標準定義の方法で多くのアクションを処理し、多くのコーナーケースを「環境に特有の方法で」処理していました。数値のオーバーフローが発生した場合にショートして発火するCPUを使用していて、CPUが発火しないようにしたい場合は、数値のオーバーフローを回避するコードを記述する必要があります。ただし、2の補数形式で値を完全に切り捨てるCPUを使用している場合、そのような切り捨てによって許容可能な動作が発生する場合にオーバーフローを回避する必要はありません。
現代のCはさらに一歩進んでいます:標準が要件を課さない数値オーバーフローのようなものの動作を自然に定義するプラットフォームをターゲットにしている場合でも、プログラムの一部のオーバーフローは他の部分の動作に影響する可能性があります時間と因果関係の法則に縛られない任意の方法でプログラム。たとえば、次のようなものを考えます:
uint32_t test(uint16_t x)
{
if (x < 50000) foo(x);
return x*x; // Note x will promote to "int" if that type is >16 bits.
}
上記のような「最新」のCコンパイラは、xが46340を超えるとx * xの計算がオーバーフローするため、「foo」の呼び出しを無条件に実行できると結論付ける可能性があります。xが範囲外の場合にプログラムを異常終了させることを許容できる場合、またはそのような場合に関数が値を返すようにしても、範囲外のxでfoo()を呼び出すと、はるかに大きな損傷を引き起こす可能性があることに注意してくださいそれらの可能性のいずれか。従来のCは、プログラマーおよび基盤となるプラットフォームが提供したものを超える安全装置を提供しませんでしたが、安全装置が予期しない状況による損傷を制限することを可能にしました。Modern Cは、すべてを制御下に保つのに100%効果のない安全装置をバイパスします。
int
32ビットであり、その後、x
署名に昇進しますint
。場合の理論的根拠から判断すると、標準の著者は非奇妙な実装は、いくつかの特定の例と同等ファッションの外側に符号付きと符号なしの型を扱うだろうと予想、しかし、GCCは、時々 、「最適化し、」ブレークという方法でuint16_t
でuint16_t
INT_MAXを超えた乗算利回り結果、結果が符号なしの値として使用されている場合でも。
-Wconversion
です。
return x+1;
問題にならないような多くの状況にフラグを立てる傾向があるため、警告は特に役立つとは思いません。結果をuint32_tにキャストすると、問題を修正せずにメッセージが抑制されます。
言語には、いくつかの正確さの層があります。抽象化の順序:
次のレベルでは、実行時ではなくコンパイル時にエラーが検出されるため、言語がより安全になります。構文的に正しいプログラムも、可能な限り意味的に正しいものでなければなりません。もちろん、コンパイラーは全体像を知ることができないため、これは詳細レベルに関係します。強力で表現力豊かなデータ型は、このレベルの安全性の1つの側面です。言語は特定の種類のエラーを作りにくくするべきだと言うことができます(タイプエラー、範囲外アクセス、初期化されていない変数など)。長さ情報を運ぶ配列のような実行時の型情報はエラーを回避します。私は大学でAda 83をプログラムしましたが、コンパイルAdaプログラムには通常、対応するCプログラムよりも桁違いに少ないエラーが含まれていることがわかりました。明示的な変換なしでは割り当てられない整数型をユーザー定義するAdaの機能を利用してください。足とメートルが混同されたために宇宙船全体がクラッシュしました。
次のレベルでは、言語は定型コードを回避する手段を提供する必要があります。独自のコンテナ、そのソート、または連結を記述する必要がある場合、または独自のコンテナを記述する必要があるstring::trim()
場合は、間違いを犯します。抽象化レベルが上がるので、この基準には適切な言語と言語の標準ライブラリが含まれます。
最近では、言語は言語レベルでの並行プログラミングの手段を提供する必要があります。並行性を正しくするのは難しく、おそらく言語サポートなしで正しく行うことは不可能です。
この言語は、モジュール化とコラボレーションの手段を提供する必要があります。上記の強力で精巧なユーザー定義型は、表現力豊かなAPIの作成に役立ちます。
言語定義はやや直交して理解できるはずです。言語とライブラリを十分に文書化する必要があります。悪いまたは欠落しているドキュメントは、悪いプログラムや間違ったプログラムにつながります。
すべてのPLに安全でないコマンドがあると言ってはいけません。安全なサブセットをいつでも取得できます。
私が知っているすべての言語には、(コンパイルして)実行できる違法なプログラムを書く方法があります。そして、私が知っているすべての言語には安全なサブセットがあります。それで、あなたの質問は本当に何ですか?
安全性は多次元的で主観的です。
一部の言語には、「安全でない」多くの操作があります。他の人はそのような操作が少ないです。一部の言語では、何かを行うデフォルトの方法は本質的に安全ではありません。その他では、デフォルトの方法が安全です。一部の言語では、明示的な「安全でない」サブセットがあります。他の言語では、そのようなサブセットはまったくありません。
一部の言語では、「安全」とはメモリの安全性のみを指します。これは、メモリアクセス違反が困難または不可能になる標準ライブラリおよび/またはランタイムが提供するサービスです。他の言語では、「安全性」にはスレッドの安全性が明示的に含まれています。他の言語では、「安全」とは、プログラムがクラッシュしないことの保証(あらゆる種類のキャッチされない例外を許可しないことを含む要件)を指します。最後に、多くの言語で「安全」とは型の安全を指します。型システムが特定の方法で一貫している場合、それは「サウンド」と呼ばれます(偶然、JavaとC#には完全に健全な型システムはありません)。
また、一部の言語では、「安全」のすべての異なる意味が型安全のサブセットと見なされます(たとえば、RustとPonyは型システムのプロパティを通じてスレッド安全を実現します)。
この答えはもう少し広範です。ここ数十年、安全と安全という言葉は、英語を話す社会の特定の政治志向の部分によって破壊されており、その一般的な用法にはほとんど定義がありません。ただし、技術的なテーマについては、「安全」と「安全」を次のように定義します。何かの意図しない使用を防止するデバイス、または偶発的な使用を大幅に困難にするデバイス、およびそのようなデバイスの保護下にある状態。
したがって、安全な言語には、特定のクラスのバグを制限するためのデバイスがあります。もちろん、制限には不便や場合によっては不可能なこともありますが、それは「安全でない」言語がバグになるということではありません。例えば、私はフォークに安全コルクを持っていませんし、何十年もの間、食事中に私の目を刺すことを避けるために多くの努力なしに管理していました。確かに、コルクを使用して費やされるよりも少ない労力。したがって、安全性には、判断する必要のあるコストが伴います。(コルクフォークはスティーブマーティンのキャラクターへの参照です)