web-dev-qa-db-ja.com

printf()の実行とセグメンテーション違反

#include<stdio.h>

int main()
{
    char *name = "Vikram";
    printf("%s",name);
    name[1]='s';
    printf("%s",name);
    return 0;
}

端末に出力が出力されず、セグメンテーション違反が発生するだけです。しかし、GDBで実行すると、次のようになります-

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400525 in main () at seg2.c:7
7       name[1]='s';
(gdb) 

これは、プログラムが7行目でSEGフォルトを受け取ることを意味します(明らかに、定数文字配列に書き込むことはできません)。では、なぜ行番号6のprintf()が実行されないのですか?

22
Vikram

これは、stdoutのストリームバッファリングが原因です。 fflush(stdout)を実行するか、改行を印刷しない限り"\n"出力はバッファリングされる場合があります。

この場合、バッファがフラッシュされて印刷される前に、segfaultingです。

代わりにこれを試すことができます:

printf("%s",name);
fflush(stdout);        //  Flush the stream.
name[1]='s';           //  Segfault here (undefined behavior)

または:

printf("%s\n",name);   //  Flush the stream with '\n'
name[1]='s';           //  Segfault here (undefined behavior)
45
Mysticial

最初に、printfsを "\ n"(または少なくとも最後のもの)で終了する必要があります。しかし、それはセグメンテーションフォールトとは関係ありません。

コンパイラーがコードをコンパイルすると、バイナリーがいくつかのセクションに分割されます。一部は読み取り専用ですが、その他は書き込み可能です。読み取り専用セクションに書き込むと、セグメンテーション違反が発生する可能性があります。文字列リテラルは通常、読み取り専用セクションに配置されます(gccは「.rodata」に配置する必要があります)。ポインター名はそのroセクションを指します。したがって、使用する必要があります

const char *name = "Vikram";

私の応答では、いくつかの「かもしれない」「すべき」を使用しました。動作は、OS、コンパイラ、およびコンパイル設定によって異なります(リンカースクリプトはセクションを定義します)。

追加

-Wa,-ahlms=myfile.lst

gccのコマンドラインに、生成されたアセンブラコードを含むmyfile.lstというファイルを作成します。上部に見えます

    .section .rodata
.LC0:
    .string "Vikram"

これは、文字列がVikramにあることを示しています。

同じコードを使用します(グローバルスコープ内にある必要があります。それ以外の場合、gccはそれをスタックに格納します。これは配列であり、ポインターではないことに注意してください)

char name[] = "Vikram";

作り出す

    .data
    .type name, @object
    .size name, 7
name:
    .string "Vikram"

構文は少し異なりますが、読み書き可能な.dataセクションにあるかどうかを確認してください。ちなみに、この例は機能します。

10
paul

セグメンテーション違反が発生する理由は、C文字列リテラルがC標準に従ってのみ読み取られ、リテラル配列「Vikram」の2番目の要素に「s」を書き込もうとしているためです。

出力が得られない理由は、プログラムが出力をバッファリングしていて、バッファをフラッシュする機会がなくなる前にクラッシュするためです。 stdioライブラリの目的は、printf(3)のような使いやすいフォーマット関数を提供することに加えて、メモリ内バッファにデータをバッファリングし、必要な場合にのみ出力をフラッシュし、時々入力のみを実行することにより、I/O操作のオーバーヘッドを減らすことです。常にではなく。一般的なケースでは、実際の入出力はstdio関数を呼び出した時点では発生せず、出力バッファーがいっぱい(または入力バッファーが空)の場合にのみ発生します。

FILEオブジェクトが設定されている場合(stderrのように)常にフラッシュする場合、状況は少し異なりますが、一般的にはGistです。

デバッグしている場合は、fprintfをstderrにして、クラッシュの前にデバッグ出力が確実にフラッシュされるようにするのが最善です。

5
Perry

デフォルトでは、stdoutが端末に接続されている場合、ストリームはラインバッファリングされます。実際には、この例では'\n'(または明示的なストリームフラッシュ)がないため、文字が印刷されません。

しかし、理論的には未定義の動作は制限されていません(標準"この国際標準が要件を課さない動作[...]から))。たとえば、未定義の動作が発生する前でもsegfaultが発生する可能性があります。最初のprintf呼び出しの前!

1
ouah