質問はそれをすべて言っていると思います。 C89からC11までのほとんどの標準をカバーする例が役立ちます。私はこの1つのものの、しかし、私はそれが単に未定義の行動だと思います:
#include <stdio.h>
int main( int argc, char* argv[] )
{
const char *s = NULL;
printf( "%c\n", s[0] );
return 0;
}
編集:
いくつかの投票が明確化を要求したように:私は通常のプログラミングエラー(私が考えることができる最も簡単なものはセグメンテーション違反)を持つプログラム、つまりguaranteedを望んでいた(標準)中止します。これは、この保険を気にしない最小のセグメンテーション違反の質問とは少し異なります。
セグメンテーション違反は、実装定義の動作です。標準は、実装が 未定義の動作 をどのように処理するかを定義しておらず、実際、実装は未定義の動作を最適化し、引き続き準拠できます。明確にするために、実装定義の動作は、標準では 指定 ではない動作ですが、実装は文書化する必要があります。 未定義の動作は、移植性がないかエラーがあるコードであり、その動作は予測できないため、信頼できないコードです。
C99ドラフト標準 §3.4.3未定義の動作を見ると、これは用語、定義、記号段落のセクション1と言う(今後のエンファシス鉱山):
この国際規格は要件を課していない
そしてパラグラフで2は言います:
注予期しない結果の可能性がある状況を完全に無視することから、環境に特有の文書化された方法での動作中の動作(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(診断メッセージの発行)。
一方、ほとんどのnix-likeシステムでセグメンテーションフォールトを引き起こす標準で定義されたメソッドが必要な場合は、raise(SIGSEGV)
がその目標を達成する必要があります。厳密に言えば、SIGSEGV
は次のように定義されます。
SIGSEGVストレージへの無効なアクセス
および§7.14信号処理<signal.h>
のコメント:
実装は、これらの信号のいずれも生成する必要はありませんが、raise関数への明示的な呼び出しの結果を除きます。 SIG文字と大文字、またはSIG_と大文字で始まるマクロ定義をそれぞれ持つ、宣言できない関数への追加のシグナルとポインターも実装で指定できます219)。 信号の完全なセット、それらのセマンティクス、およびデフォルトの処理は、実装で定義されています;すべての信号番号は正でなければなりません。
raise()
を使用して、セグメンテーション違反を発生させることができます。
raise(SIGSEGV);
標準では未定義の動作のみに言及しています。メモリのセグメンテーションについては何も知りません。また、エラーを生成するコードは標準に準拠していないことに注意してください。コードで未定義の動作を呼び出したり、同時に標準に準拠したりすることはできません。
それにもかかわらず、doがそのようなフォールトを生成するアーキテクチャでセグメンテーションフォールトを生成する最短の方法は次のとおりです。
int main()
{
*(int*)0 = 0;
}
なぜこれでセグメンテーション違反が発生するのでしょうか?メモリアドレス0へのアクセスは常にシステムによってトラップされるためです。有効なアクセスになることはありません(少なくともユーザー空間コードによるものではありません)。
もちろん、すべてのアーキテクチャが同じように機能するわけではないことに注意してください。それらのいくつかでは、上記はまったくクラッシュしませんでしたが、他の種類のエラーを生成しました。または、ステートメントが完全に問題なくても、メモリロケーション0に問題なくアクセスできます。これは、標準が実際に何が起こるかを定義しない理由の1つです。
正しいプログラムではセグメンテーション違反は発生しません。また、誤ったプログラムの決定論的な動作を記述することはできません。
「セグメンテーションフォールト」とは、x86 CPUが行うことです。間違った方法でメモリを参照しようとすると、それを取得します。また、メモリアクセスがページフォールト(つまり、ページテーブルにロードされていないメモリにアクセスしようとする)を引き起こし、OSがそのメモリをリクエストする権利がないと判断する状況も参照できます。これらの条件をトリガーするには、OSおよびハードウェア用に直接プログラムする必要があります。 C言語で指定されているものは何もありません。
raise
を呼び出す信号を発生させないと仮定すると、セグメンテーション違反は未定義の動作に起因する可能性があります。未定義の動作は未定義であり、コンパイラーは翻訳を拒否することができるため、すべての実装で未定義の回答が失敗することはありません。さらに、未定義の動作を呼び出すプログラムはエラーのあるプログラムです。
しかし、これはmyシステムでそのセグメンテーションフォールトを取得できる最短です:
main(){main();}
(gcc
および-std=c89 -O0
でコンパイルします)。
ところで、このプログラムは未定義のbevahiorを実際に呼び出しますか?
一部のプラットフォームでは、システムに大量のリソースを要求すると、標準準拠のCプログラムがセグメンテーションフォールトで失敗する場合があります。たとえば、malloc
を使用して大きなオブジェクトを割り当てると成功したように見えますが、後でオブジェクトにアクセスするとクラッシュします。
このようなプログラムは厳密にに準拠していないことに注意してください。その定義を満たすプログラムは、それぞれの最小実装制限内に収まらなければなりません。
他の方法は未定義の動作を経由するため、標準準拠のCプログラムはセグメンテーションフォールトを生成できません。
SIGSEGV
シグナルは明示的に発生させることができますが、標準CライブラリにはSIGSEGV
シンボルはありません。
(この回答では、「標準準拠」とは、「ISO C標準の一部のバージョンで説明されている機能のみを使用し、不特定、実装定義または未定義の動作を回避しますが、必ずしも最小実装制限に限定されません」)
この質問に対する答えのほとんどは、次のような重要な点について話している:C標準には、セグメンテーション違反の概念は含まれていません。( C99にはシグナル番号SIGSEGV
が含まれていますが、raise(SIGSEGV)
、他の回答で説明したようにカウントされません。)
したがって、セグメンテーションフォールトを引き起こすことが保証される「厳密に適合する」プログラム(つまり、動作がC標準によって完全に定義されている構造のみを使用するプログラム)はありません。
セグメンテーションフォールトは、異なる標準 [〜#〜] posix [〜#〜] によって定義されます。このプログラムは、メモリ保護および高度なリアルタイムオプションを含むPOSIX.1-2008に完全に準拠しているシステムで、セグメンテーションフォールトまたは機能的に同等の「バスエラー」(SIGBUS
)を引き起こすことが保証されています。 sysconf
、posix_memalign
およびmprotect
は成功します。私のC99の読み方は、このプログラムはimplementation-defined(未定義ではない!)の振る舞いであり、その標準のみを考慮しているため、conformingただし、strictly conformingではありません。
#define _XOPEN_SOURCE 700
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(void)
{
size_t pagesize = sysconf(_SC_PAGESIZE);
if (pagesize == (size_t)-1) {
fprintf(stderr, "sysconf: %s\n", strerror(errno));
return 1;
}
void *page;
int err = posix_memalign(&page, pagesize, pagesize);
if (err || !page) {
fprintf(stderr, "posix_memalign: %s\n", strerror(err));
return 1;
}
if (mprotect(page, pagesize, PROT_NONE)) {
fprintf(stderr, "mprotect: %s\n", strerror(errno));
return 1;
}
*(long *)page = 0xDEADBEEF;
return 0;
}
セグメンテーションフォールト未定義のプラットフォーム上のプログラムにメソッドを定義するのは困難です。 セグメンテーションフォールトは、すべてのプラットフォーム(単純な小型コンピューターなど)に対して定義されていない緩やかな用語です。
processesをサポートするオペレーティングシステムのみを考慮すると、プロセスはセグメンテーションエラーが発生したという通知を受け取ることができます。
さらに、オペレーティングシステムを「Unixのような」OSに制限する場合、SIGSEGV信号を受信するプロセスの信頼できる方法はkill(getpid(),SIGSEGV)
です
ほとんどのクロスプラットフォームの問題の場合と同様に、各プラットフォームには、セグメンテーション違反の定義が異なる場合があります(通常はそうなります)。
しかし、実用的であるために、現在のMac、Lin、およびWin OSはセグメンテーション違反になります
_*(int*)0 = 0;
_
さらに、セグメンテーション違反を引き起こすことは悪い動作ではありません。 assert()
の実装によっては、コアファイルを生成する可能性のあるSIGSEGVシグナルが発生します。剖検が必要な場合に非常に便利です。
セグメンテーション違反を引き起こすよりも悪いのは、それを隠すことです:
_try
{
anyfunc();
}
catch (...)
{
printf("?\n");
}
_
これはエラーの原因を隠しており、あなたがしなければならないことは次のとおりです:
_?
_
。
main;
それでおしまい。
本当に。
基本的に、これはmain
をvariableとして定義しています。 Cでは、変数と関数は両方ともsymbols-メモリ内のポインターであるため、コンパイラーはそれらを区別せず、このコードはエラーをスローしません。
ただし、問題はsystemが実行可能ファイルを実行する方法にあります。簡単に言えば、C標準では、すべてのC実行可能ファイルに環境を準備するエントリポイントが組み込まれている必要があります。これは、基本的に「call main
」に要約されます。
ただし、この特定のケースでは、main
は変数であるため、変数を対象とする.bss
と呼ばれる非実行可能メモリのセクションに配置されます( .text
はコード用)。 .bss
でコードを実行しようとすると、特定のセグメンテーションに違反するため、システムはセグメンテーションフォールトをスローします。
例として、結果ファイルのobjdump
を以下に示します(の一部):
# (unimportant)
Disassembly of section .text:
0000000000001020 <_start>:
1020: f3 0f 1e fa endbr64
1024: 31 ed xor %ebp,%ebp
1026: 49 89 d1 mov %rdx,%r9
1029: 5e pop %rsi
102a: 48 89 e2 mov %rsp,%rdx
102d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1031: 50 Push %rax
1032: 54 Push %rsp
1033: 4c 8d 05 56 01 00 00 lea 0x156(%rip),%r8 # 1190 <__libc_csu_fini>
103a: 48 8d 0d df 00 00 00 lea 0xdf(%rip),%rcx # 1120 <__libc_csu_init>
# This is where the program should call main
1041: 48 8d 3d e4 2f 00 00 lea 0x2fe4(%rip),%rdi # 402c <main>
1048: ff 15 92 2f 00 00 callq *0x2f92(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
104e: f4 hlt
104f: 90 nop
# (Nice things we still don't care about)
Disassembly of section .data:
0000000000004018 <__data_start>:
...
0000000000004020 <__dso_handle>:
4020: 20 40 00 and %al,0x0(%rax)
4023: 00 00 add %al,(%rax)
4025: 00 00 add %al,(%rax)
...
Disassembly of section .bss:
0000000000004028 <__bss_start>:
4028: 00 00 add %al,(%rax)
...
# main is in .bss (variables) instead of .text (code)
000000000000402c <main>:
402c: 00 00 add %al,(%rax)
...
# aaand that's it!
PS:フラットな実行可能ファイルにコンパイルする場合、これは機能しません。代わりに、未定義の動作が発生します。
最小文字数を考慮した最も単純な形式は次のとおりです。
++*(int*)0;