69

質問がすべてを物語っていると思います。C89 から C11 までのほとんどの標準をカバーする例が役立ちます。私はこれについて考えましたが、それは未定義の動作だと思います:

#include <stdio.h>

int main( int argc, char* argv[] )
{
  const char *s = NULL;
  printf( "%c\n", s[0] );
  return 0;
}

編集:

一部の投票で明確化が求められたため、通常のプログラミング エラー (考えられる最も単純なものは segfault でした) があり、(標準で) 中止されることが保証されているプログラムが必要でした。これは、この保険を気にしない最小セグメンテーションの質問とは少し異なります。

4

10 に答える 10

72

セグメンテーション違反は、実装定義の動作です。標準は、実装が未定義の動作を処理する方法を定義していません。実際、実装は未定義の動作を最適化しても準拠している可能性があります。明確にするために、実装定義の動作は、標準では指定されていませんが、実装で文書化する必要がある動作です。未定義の動作とは、移植性がない、または誤りがあり、その動作が予測できないため信頼できないコードです。

C99 ドラフト標準§3.4.3未定義の動作を見ると、段落1用語、定義、および記号のセクションに含まれています (強調は今後に向けて):

この国際規格が要件を課していない、移植性のない、または誤ったプログラム構造または誤ったデータの使用時の動作

そしてパラグラフ2で言う:

注記 未定義の可能性のある動作は、状況を完全に無視して予測不能な結果を​​もたらすことから、変換中またはプログラム実行中に環境に特有の文書化された方法で動作すること (診断メッセージの発行の有無にかかわらず)、変換または実行の終了 (診断メッセージの発行の有無にかかわらず) にまで及びます。診断メッセージの発行)。

一方、ほとんどのUnix系システムでセグメンテーション違反を引き起こす標準で定義されたメソッドが必要な場合raise(SIGSEGV)は、その目標を達成する必要があります。ただし、厳密にSIGSEGVは次のように定義されています。

SIGSEGV ストレージへの無効なアクセス

および§7.14シグナル処理<signal.h>は次のように述べています。

実装は、raise 関数への明示的な呼び出しの結果を除いて、これらのシグナルを生成する必要はありません。SIG と大文字、または SIG_ と大文字で始まるマクロ定義を持つ、宣言できない関数への追加のシグナルとポインタ 219) も、実装によって指定される場合があります。シグナルの完全なセット、そのセマンティクス、およびデフォルトの処理は実装定義です。すべてのシグナル番号は正でなければなりません。

于 2013-09-24T15:51:44.057 に答える
27

標準では、未定義の動作についてのみ言及しています。メモリのセグメンテーションについては何も知りません。エラーを生成するコードは標準に準拠していないことにも注意してください。コードは、未定義の動作を呼び出し、同時に標準に準拠することはできません。

それにもかかわらず、そのような障害を生成するアーキテクチャでセグメンテーション障害を生成する最短の方法は次のとおりです。

int main()
{
    *(int*)0 = 0;
}

これが確実にセグメンテーション違反を引き起こすのはなぜですか? メモリ アドレス 0 へのアクセスは常にシステムによってトラップされるためです。有効なアクセスになることはありません (少なくともユーザー空間コードによるものではありません)。

もちろん、すべてのアーキテクチャが同じように機能するわけではないことに注意してください。それらのいくつかでは、上記はまったくクラッシュせず、むしろ他の種類のエラーを生成する可能性がありました. または、ステートメントは完全に問題なく、メモリ位置 0 に問題なくアクセスできます。これが、標準が実際に何が起こるかを定義していない理由の 1 つです。

于 2013-09-24T15:54:32.517 に答える
13

正しいプログラムは、segfault を生成しません。また、正しくないプログラムの決定論的な動作を説明することはできません。

「セグメンテーション違反」は、x86 CPU が行うことです。間違った方法でメモリを参照しようとすると、それを取得します。また、メモリ アクセスによってページ フォールトが発生し (つまり、ページ テーブルに読み込まれていないメモリにアクセスしようとする)、そのメモリを要求する権利がないと OS が判断する状況を指す場合もあります。これらの条件をトリガーするには、OS とハードウェアを直接プログラムする必要があります。C言語で規定されているものではありません。

于 2013-09-24T15:51:43.470 に答える
8

信号呼び出しを発生させていないと仮定すると raise、セグメンテーション違反は未定義の動作に起因する可能性があります。未定義の動作は未定義であり、コンパイラは自由に変換を拒否できるため、未定義の回答がすべての実装で失敗する保証はありません。さらに、未定義の動作を呼び出すプログラムは、エラーのあるプログラムです。

しかし、これは私のシステムでそのセグメンテーション違反を取得できる最短のものです:

main(){main();}

gcc(私はandでコンパイルします-std=c89 -O0)。

ところで、このプログラムは本当に未定義の動作を呼び出すのでしょうか?

于 2013-09-24T21:05:30.027 に答える
2

一部のプラットフォームでは、標準準拠の C プログラムは、システムに要求するリソースが多すぎると、セグメンテーション違反で失敗する可能性があります。たとえば、大きなオブジェクトの割り当てはmalloc成功したように見えても、後でオブジェクトにアクセスするとクラッシュします。

そのようなプログラムは厳密に準拠していないことに注意してください。その定義を満たすプログラムは、実装の最小制限のそれぞれにとどまる必要があります。

それ以外の場合、標準準拠の C プログラムはセグメンテーション違反を生成できません。それ以外の唯一の方法は、未定義の動作によるものだからです。

SIGSEGVシグナルは明示的に発生させることができますが、標準 C ライブラリにはシンボルがありませんSIGSEGV

(この回答では、「標準準拠」とは、「ISO C標準の一部のバージョンで説明されている機能のみを使用し、未指定、実装定義、または未定義の動作を回避しますが、必ずしも実装の最小制限に限定されるわけではありません.」を意味します。)

于 2013-09-24T19:08:46.593 に答える
1

この質問への回答のほとんどは、次の重要なポイントについて述べています。C 標準には、セグメンテーション違反の概念が含まれていません。 (C99以降、シグナル番号 が含まれていますが、他の回答で説明されているようにカウントされないSIGSEGV以外、そのシグナルが配信される状況は定義されていません。)raise(SIGSEGV)

したがって、セグメンテーション違反を引き起こすことが保証されている "厳密に準拠した" プログラム (つまり、動作が C 標準によって完全に定義されている構造のみを使用するプログラム) はありません。

セグメンテーション違反は、別の標準であるPOSIXによって定義されています。SIGBUSこのプログラムは、メモリ保護および高度なリアルタイム オプションを含む POSIX.1-2008 に完全に準拠しているシステムでは、セグメンテーション フォールトまたは機能的に同等の「バス エラー」( ) を引き起こすことが保証されていますsysconfposix_memalignそしてmprotect成功します。私のC99の解釈は、このプログラムはその標準のみを考慮した実装定義の(未定義ではない!)動作をしているため、準拠していますが、厳密には準拠していないということです。

#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;
}
于 2016-05-04T15:29:46.583 に答える