コンピューティングにおいて、セグメンテーション違反(segfaultと略されることが多い)またはアクセス違反は、メモリ保護機能を持つハードウェアによって発生する障害または故障状態であり、オペレーティングシステム(OS)に、ソフトウェアがメモリの制限された領域にアクセスしようとしたこと(メモリアクセス違反)を通知します。標準のx86コンピュータでは、これは一般保護違反の一種です。これに対して、オペレーティングシステムカーネルは通常、何らかの修正アクションを実行し、一般的にはプロセスにシグナルを送信して、問題のあるプロセスに障害を渡します。プロセスは、場合によってはカスタムシグナルハンドラーをインストールして、自力で回復できるようにすることができますが、[1]そうでない場合はOSのデフォルトのシグナルハンドラーが使用され、一般的にはプロセスの異常終了(プログラムクラッシュ)や、場合によってはコアダンプが発生します。
セグメンテーション違反は、低レベルのメモリ アクセスと安全性チェックがほとんどまたはまったくないCなどの言語で記述されたプログラムでよく見られるエラーです。主に仮想メモリのアドレス指定にポインターを使用する際のエラー、特に不正なアクセスが原因で発生します。別の種類のメモリ アクセス エラーはバス エラーです。バス エラーにもさまざまな原因がありますが、現在では非常にまれです。主に物理メモリのアドレス指定が間違っているか、メモリ アクセスが不整合なために発生します。これらは、プロセスがアドレス指定できないメモリ参照ではなく、ハードウェアがアドレス指定できないメモリ参照です。
多くのプログラミング言語には、セグメンテーションフォールトを回避し、メモリの安全性を向上させるためのメカニズムが備わっています。たとえば、Rustは所有権ベース[2]モデルを採用してメモリの安全性を確保しています。[3] LispやJavaなどの他の言語では、ガベージコレクション[4]を採用しており、セグメンテーションフォールトにつながる可能性のある特定の種類のメモリエラーを回避します。[5]
概要



セグメンテーション エラーは、プログラムがアクセスが許可されていないメモリ位置にアクセスしようとした場合、または許可されていない方法でメモリ位置にアクセスしようとした場合 (たとえば、読み取り専用の位置への書き込みを試みた場合や、オペレーティング システムの一部を上書きしようとした場合) に発生します。
「セグメンテーション」という用語は、コンピューティングにおいてさまざまな用途があります。「セグメンテーション違反」の文脈では、プログラムのアドレス空間を指します。 [6]メモリ保護では、プログラム自身のアドレス空間のみが読み取り可能であり、そのうちスタックとプログラムのデータセグメントの読み取り/書き込み部分のみが書き込み可能であり、constセグメントとコードセグメントに割り当てられた読み取り専用データは書き込みできません。したがって、プログラムのアドレス空間の外部を読み取ろうとしたり、アドレス空間の読み取り専用セグメントに書き込んだりすると、セグメンテーション違反が発生するため、この名前が付けられています。
仮想メモリを提供するためにハードウェアメモリ セグメンテーションを使用するシステムでは、存在しないセグメントを参照しようとしたり、セグメントの境界外の場所を参照しようとしたり、そのセグメントに付与された権限で許可されていない方法で場所を参照しようとしたりすることをハードウェアが検出すると、セグメンテーション エラーが発生します。ページングのみを使用するシステムでは、通常、無効ページ フォールトによってセグメンテーション エラーが発生します。セグメンテーション エラーとページ フォールトは、どちらも仮想メモリ管理システムによって発生するエラーです。 セグメンテーション エラーは、ページ フォールトとは関係なく発生することもあります。有効なページへの不正アクセスはセグメンテーション エラーですが、無効ページ フォールトではありません。また、セグメンテーション エラーは、ページの途中で発生することもあります (したがって、ページ フォールトではありません)。たとえば、ページ内にとどまりながらメモリを不正に上書きする バッファ オーバーフローなどです。
ハードウェア レベルでは、メモリ管理ユニット(MMU) によって、メモリ保護機能の一部として不正アクセス (参照メモリが存在する場合) または無効なページ フォールト (参照メモリが存在しない場合) が発生したときに最初に障害が発生します。問題が無効な論理アドレスではなく無効な物理アドレスである場合は、代わりにバス エラーが発生しますが、これらは必ずしも区別されるわけではありません。
オペレーティング システム レベルでは、この障害がキャッチされ、問題のあるプロセスにシグナルが渡され、そのシグナルに対するプロセスのハンドラーがアクティブ化されます。オペレーティング システムによって、セグメンテーション障害が発生したことを示すシグナル名が異なります。Unix系オペレーティング システムでは、SIGSEGV (セグメンテーション違反の略) と呼ばれるシグナルが問題のあるプロセスに送信されます。Microsoft Windowsでは、問題のあるプロセスは STATUS_ACCESS_VIOLATION例外を受け取ります。
原因
セグメンテーション違反が発生する条件や、それがどのように現れるかは、ハードウェアとオペレーティング システムに固有です。ハードウェアが異なれば、特定の条件に対して発生するエラーも異なり、オペレーティング システムも異なれば、これをプロセスに渡される信号に変換します。直接的な原因はメモリ アクセス違反ですが、根本的な原因は通常、何らかのソフトウェア バグです。根本原因の特定(バグのデバッグ) は、プログラムが常にセグメンテーション違反を引き起こす場合 (例:ヌル ポインターの逆参照) は簡単な場合もありますが、バグの再現が難しく、実行のたびにメモリ割り当てが左右される場合もあります (例:ダングリング ポインターの逆参照)。
セグメンテーション エラーの一般的な原因は次のとおりです。
- 存在しないメモリアドレス(プロセスのアドレス空間外)にアクセスしようとしています
- プログラムが権限を持たないメモリにアクセスしようとする(プロセスコンテキスト内のカーネル構造など)
- 読み取り専用メモリ(コードセグメントなど)への書き込みの試行
これらは多くの場合、無効なメモリ アクセスを引き起こすプログラミング エラーによって発生します。
- 通常はプロセスのアドレス空間の一部ではないアドレスを指すヌルポインタの逆参照
- 初期化されていないポインタ(ランダムなメモリアドレスを指すワイルドポインタ)への参照解除または代入
- 解放されたポインタ(解放/割り当て解除/削除されたメモリを指すダングリングポインタ)への参照解除または代入
- バッファオーバーフロー
- スタックオーバーフロー
- 正しくコンパイルされないプログラムを実行しようとしています。(一部のコンパイラ[どれですか? ]は、コンパイル時エラーが存在するにもかかわらず、実行可能ファイルを出力します。)
C コードでは、セグメンテーション エラーは、ポインターの使用、特にC の動的メモリ割り当てにおけるエラーが原因で最も頻繁に発生します。未定義の動作を引き起こす NULL ポインターの逆参照は、通常、セグメンテーション エラーの原因となります。これは、NULL ポインターが有効なメモリ アドレスになることができないためです。一方、ワイルド ポインターとダングリング ポインターは、存在するかどうかわからないメモリ、読み取りまたは書き込み可能かどうかわからないメモリを指しているため、一時的なバグが発生する可能性があります。例:
char * p1 = NULL ; // ヌルポインタchar * p2 ; // ワイルドポインタ: まったく初期化されていません。char * p3 = malloc ( 10 * sizeof ( char )); // 割り当てられたメモリへの初期化されたポインタ// (malloc が失敗していないと仮定) free ( p3 ); // メモリが解放されたため、p3 はダングリングポインタになりました
これらの変数のいずれかを逆参照すると、セグメンテーション違反が発生する可能性があります。ヌル ポインターを逆参照すると、通常はセグメント違反が発生しますが、ワイルド ポインターからの読み取りでは、代わりにランダム データが生成されますが、セグメント違反は発生しません。また、ダングリング ポインターからの読み取りでは、しばらくは有効なデータが生成されますが、その後、上書きされるとランダム データが生成されます。
取り扱い
セグメンテーション エラーまたはバス エラーに対するデフォルトのアクションは、そのエラーの原因となったプロセスを異常終了することです。デバッグを支援するためにコア ファイルが生成される場合があり、その他のプラットフォーム依存のアクションも実行されることがあります。たとえば、 grsecurity パッチを使用するLinuxシステムでは、バッファ オーバーフローを使用した侵入の試みを監視するために SIGSEGV 信号がログに記録される場合があります。
LinuxやWindowsなどの一部のシステムでは、プログラム自体がセグメンテーション違反を処理できます。[7]アーキテクチャとオペレーティングシステムによっては、実行中のプログラムはイベントを処理できるだけでなく、スタックトレース、プロセッサレジスタ値、トリガーされたときのソースコードの行、無効にアクセスされたメモリアドレス[8]、アクションが読み取りか書き込みかなどの状態に関する情報を抽出できる場合があります。[9]
セグメンテーション違反は、一般的にはプログラムに修正が必要なバグがあることを意味しますが、テストやデバッグ、またメモリへの直接アクセスが必要なプラットフォームをエミュレートする目的で、意図的にそのような違反を引き起こすことも可能です。後者の場合、システムは違反が発生した後でもプログラムの実行を許可できなければなりません。この場合、システムが許可すれば、イベントを処理し、プロセッサのプログラムカウンタを増分して違反した命令を「飛び越えて」実行を続行することが可能です。[10]
例

読み取り専用メモリへの書き込み
読み取り専用メモリに書き込むと、セグメンテーション エラーが発生します。コード エラーのレベルでは、プログラムが自身のコード セグメントの一部またはデータ セグメントの読み取り専用部分に書き込むと、OS によって読み取り専用メモリに読み込まれるため、セグメンテーション エラーが発生します。
以下は、メモリ保護機能を備えたプラットフォームで一般的にセグメンテーション違反を引き起こすANSI Cコードの例です。文字列リテラルを変更しようとしていますが、これは ANSI C 標準では未定義の動作です。ほとんどのコンパイラはコンパイル時にこれを検出せず、代わりにクラッシュする実行可能コードにコンパイルします。
int main ( void ) { char * s = "hello world" ; * s = 'H' ; }
このコードを含むプログラムをコンパイルすると、文字列「hello world」がプログラム実行可能ファイルのrodataセクション (データ セグメントの読み取り専用セクション)に配置されます。ロードされると、オペレーティング システムはこれを他の文字列および定数データとともにメモリの読み取り専用セグメントに配置します。実行されると、変数sが文字列の場所を指すように設定され、変数を介してH文字をメモリに書き込もうとするため、セグメンテーション エラーが発生します。このようなプログラムを、コンパイル時に読み取り専用場所の割り当てをチェックしないコンパイラでコンパイルし、Unix 系オペレーティング システムで実行すると、次の実行時エラーが発生します。
$ gcc segfault.c -g -o segfault
$ ./segfault
セグメンテーション違反
プログラムはシグナルSIGSEGVを受信しました。セグメンテーションエラーです。main ()で0x1c0005c2 が発生しました。c : 6 6 * s = ' H' ;
このコードは、文字ポインターの代わりに配列を使用することで修正できます。これにより、スタック上にメモリが割り当てられ、文字列リテラルの値に初期化されます。
char s [] = "hello world" ; s [ 0 ] = 'H' ; // 同様に、 *s = 'H';
文字列リテラルは変更すべきではない(C標準では未定義の動作となる)が、Cではそれらはstatic char []型であり、[11] [12] [13]なので元のコード(その配列を指す)には暗黙的な変換はないchar *。一方、C++ではそれらはstatic const char []型であり暗黙的な変換があり、コンパイラは通常この特定のエラーをキャッチする。
ヌルポインタ参照
C および C に似た言語では、ヌル ポインタは「オブジェクトへのポインタではない」という意味で、またエラー インジケーターとして使用されます。ヌル ポインタの逆参照(ヌル ポインタを介した読み取りまたは書き込み) は、非常に一般的なプログラム エラーです。C 標準では、ヌル ポインタがメモリ アドレス 0 へのポインタと同じであるとは規定されていませんが、実際にはそうである可能性があります。ほとんどのオペレーティング システムでは、ヌル ポインタのアドレスをマップして、アクセスするとセグメンテーション エラーが発生します。この動作は、C 標準では保証されていません。ヌル ポインタの逆参照は、 C では未定義の動作であり、準拠した実装では、逆参照されるポインタはヌルではないと想定できます。
int * ptr = NULL ; printf ( "%d" , * ptr );
このサンプル コードは、ヌル ポインターを作成し、その値にアクセス (値を読み取ろうと) します。これを行うと、多くのオペレーティング システムで実行時にセグメンテーション エラーが発生します。
ヌル ポインターを逆参照してからそれに割り当てる (存在しないターゲットに値を書き込む) と、通常はセグメンテーション エラーが発生します。
int * ptr = NULL ; * ptr = 1 ;
次のコードには null ポインターの逆参照が含まれていますが、値が未使用であり、そのため、逆参照はデッド コードの削除によって最適化されるため、コンパイル時にセグメンテーション エラーが発生することはほとんどありません。
int * ptr = NULL ; * ptr ;
バッファオーバーフロー
s次のコードは、上限を超えて
文字配列にアクセスします。コンパイラとプロセッサによっては、セグメンテーション エラーが発生する可能性があります。
char s [] = "hello world" ; char c = s [ 20 ];
スタックオーバーフロー
もう 1 つの例は、基本ケースのない 再帰です。
int main ( void ) { main ( )を返します。 }
これによりスタックがオーバーフローし、セグメンテーション違反が発生します。[14] 言語、コンパイラによって実行される最適化、およびコードの正確な構造によっては、無限再帰が必ずしもスタックオーバーフローを引き起こすとは限りません。この場合、到達不可能なコード(return文)の動作は未定義であるため、コンパイラはそれを削除し、スタックを使用しない可能性がある末尾呼び出し最適化を使用できます。他の最適化には、再帰を反復に変換することが含まれます。これにより、例の関数の構造を考えると、プログラムは永久に実行されますが、スタックがオーバーフローする可能性は低くなります。
参照
参考文献
- ^ エキスパート C プログラミング: 深い C の秘密Peter Van der Linden 著、188 ページ
- ^ 「Rustプログラミング言語 - 所有権」。
- ^ 「Rust による大胆な並行性 - Rust プログラミング言語ブログ」。
- ^ McCarthy, John (1960年4月). 「記号式の再帰関数と機械による計算、パートI」Communications of the ACM . 4 (3): 184–195. doi : 10.1145/367177.367199 . S2CID 1489409 . 2018年9月22日閲覧。
- ^ Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris (2003 年 1 月 1 日)。「実行時チェックやガベージ コレクションのないメモリ安全性」(PDF)。組み込みシステム向け言語、コンパイラ、ツールに関する 2003 ACM SIGPLAN 会議の議事録。第 38 巻。ACM。pp. 69–80。doi : 10.1145 /780732.780743。ISBN 1581136471. S2CID 1459540 . 2018年9月22日閲覧。
- ^ 「セグメンテーション違反とポインターの問題のデバッグ - Cprogramming.com」www.cprogramming.com . 2021年2月3日閲覧。
- ^ 「Windows および Linux (32 ビット、x86) でのセグメント違反からのクリーンな回復」。2020 年 8 月 23 日閲覧。
- ^ 「デバッグ スタック トレースを出力する SIGSEGV/SIGABRT ハンドラーの実装」。GitHub。2020年 8 月 23 日閲覧。
- ^ 「SIGSEGV で sigaction ハンドラーを使用しているときにページフォールトの読み取りまたは書き込み操作を識別する方法 (LINUX)」 。 2020 年 8 月 23 日閲覧。
- ^ 「LINUX – WRITING FAULT HANDLERS」. 2017年11月12日. 2020年8月23日閲覧。
- ^ 「6.1.4 文字列リテラル」。ISO /IEC 9899:1990 - プログラミング言語 - C。
- ^ 「6.4.5 文字列リテラル」。ISO /IEC 9899:1999 - プログラミング言語 - C。
- ^ 「6.4.5 文字列リテラル」。ISO/IEC 9899:2011 - プログラミング言語 -- C。
- ^ 「セグメンテーション違反とスタックオーバーフローの違いは何ですか?」。Stack Overflow 。 2023年11月11日閲覧。
外部リンク
- プロセス: フォーカス境界とセグメンテーション違反[リンク切れ ]
- FAQ: セグメンテーション違反の定義に関するユーザーからの回答
- 「ヌルポインタ」の説明
- 回答: NULL は 0 になることが保証されていますが、ヌル ポインターはそうではありませんか?
- オープン グループ ベース仕様 発行 6 signal.h
