web-dev-qa-db-ja.com

「int * nums = {5、2、1、4}」によりセグメンテーションエラーが発生する

int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);

セグメンテーション違反を引き起こしますが、

int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);

しません。今:

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);

プリント5。

これに基づいて、配列初期化表記{}は、このデータを左側の変数に盲目的にロードすると推測しました。 int []の場合、配列は必要に応じて埋められます。 int *の場合、ポインタは5で埋められ、ポインタが格納された後のメモリ位置は2、1、および4で埋められます。したがって、nums [0]は5の逆参照を試行し、セグメンテーション違反が発生します。

私が間違っている場合、私を修正してください。そして、正しい場合は、配列初期化子がそのように機能する理由がわからないため、詳しく説明してください。

81
user1299784

Cには、配列であるかのように、ブレースで囲まれた初期化子リストでプレーン変数を初期化できるという(愚かな)ルールがあります。

たとえば、int x = {0};と完全に同等のint x = 0;を記述できます。

したがって、int *nums = {5, 2, 1, 4};を記述すると、実際には単一のポインタ変数に初期化子リストが与えられます。ただし、これは1つの変数であるため、最初の値5のみが割り当てられ、リストの残りの部分は無視されます(実際、過剰な初期化子を含むコードは厳密なコンパイラでコンパイルする必要はないと思います)-まったくメモリに書き込まれます。コードはint *nums = 5;と同等です。つまり、numsaddress5を指す必要があります。

この時点で、すでに2つのコンパイラの警告/エラーが発生しているはずです。

  • キャストなしで整数をポインターに割り当てます。
  • 初期化子リストの余分な要素。

そしてもちろん、5nums[0]で逆参照することを許可されている有効なアドレスではない可能性が高いため、コードはクラッシュして書き込みます。

補足として、printfポインターアドレスは%p指定子を使用する必要があります。そうでない場合は、未定義の動作を呼び出します。


ここで何をしようとしているのかわかりませんが、配列を指すようにポインタを設定する場合は、次のようにする必要があります。

int nums[] = {5, 2, 1, 4};
int* ptr = nums;

// or equivalent:
int* ptr = (int[]){5, 2, 1, 4};

または、ポインターの配列を作成する場合:

int* ptr[] = { /* whatever makes sense here */ };

[〜#〜] edit [〜#〜]

いくつかの調査の後、「余分な要素の初期化リスト」は実際には有効なCではないと言うことができます。これは GCC拡張 です。

標準6.7.9 Initializationは(emphasis mine)と言います:

2 No initializer shall attempt to provide a value for an object not contained within the entity being initialized.

/-/

11スカラーの初期化子は、オプションで中括弧で囲まれた単一の式でなければなりません。オブジェクトの初期値は、式の初期値です(変換後) ;単純な割り当てと同じ型の制約と変換が適用され、スカラーの型は宣言された型の非修飾バージョンになります。

「スカラー型」は、配列、構造体、またはユニオン型ではない単一の変数を指す標準用語です(これらは「集約型」と呼ばれます)。

そのため、英語の標準では、「変数を初期化するときは、可能だからといって、イニシャライザ式の周りに余分な括弧を入れて自由に投げてください。」と書かれています。

113
Lundin

シナリオ1

int *nums = {5, 2, 1, 4};    // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]);    // segfault

なぜこれがセグメンテーション違反なのでしょうか?

numsをintへのポインタとして宣言しました。つまり、numsはメモリ内でone integerのアドレスを保持することになっています。

次に、nums複数値の配列に初期化しようとしました。したがって、詳細を掘り下げることなく、これは概念的に間違っていますです。1つの値を保持するはずの変数に複数の値を割り当てることは意味がありません。この点で、これを行うとまったく同じ効果が得られます。

int nums = {5, 2, 1, 4};    // <-- assign multiple values to an int variable
printf("%d\n", nums);    // also print 5

いずれの場合(ポインターまたはint変数に複数の値を割り当てる)、その後に起こることは、変数が5である最初の値を取得し、残りの値は無視されることです。このコードは準拠していますが、割り当てに含まれていない追加の値ごとに警告が表示されます。

warning: excess elements in scalar initializer

ポインター変数に複数の値を割り当てる場合、nums[0]にアクセスするとプログラムはセグメンテーション違反になります。つまり、アドレス5に文字通り格納されているものはすべて延期されます。この場合、ポインタnumsに有効なメモリを割り当てませんでした。

Int変数に複数の値を割り当てる場合、セグメンテーション違反がないことに注意する価値があります(ここでは無効なポインターを間接参照していません)。


シナリオ2

int nums[] = {5, 2, 1, 4};

スタック内の4つのintの配列を合法的に割り当てているため、これはセグメンテーション違反ではありません。


シナリオ

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);   // print 5

これは期待どおりにセグメンテーション違反になりません。これは、ポインタ自体の値-参照解除するもの(無効なメモリアクセス)ではないためです。


その他

あなたがいつでもセグメンテーションフォールトする運命にある ポインターの値をハードコードする このように(どのプロセスがどのメモリ位置にアクセスできるかを決定するのはオペレーティングシステムのタスクだからです)。

int *nums = 5;    // <-- segfault

そのため、経験則として、次のようなallocated変数のアドレスへのポインターを常に初期化することです。

int a;
int *nums = &a;

または、

int a[] = {5, 2, 1, 4};
int *nums = a; 
28
artm

int *nums = {5, 2, 1, 4};は不正な形式のコードです。このコードを次と同じように扱うGCC拡張機能があります。

int *nums = (int *)5;

メモリアドレス5へのポインターを作成しようとしています(これは、私にとって便利な拡張機能ではないようですが、開発者ベースがそれを望んでいると思います)。

この動作を回避する(または少なくとも警告を表示する)には、標準モードでコンパイルできます。 -std=c11 -pedantic

有効なコードの代替形式は次のとおりです。

int *nums = (int[]){5, 2, 1, 4};

numsと同じ保存期間の可変リテラルを指します。しかし int nums[]バージョンは、使用するストレージが少ないため、一般的に優れています。また、sizeofを使用して、配列の長さを検出できます。

25
M.M
int *nums = {5, 2, 1, 4};

numsint型のポインターです。したがって、このポイントを有効なメモリの場所に設定する必要があります。 num[0]ランダムなメモリの場所を間接参照しようとしているため、セグメンテーションエラーが発生しています。

はい、ポインタは値5を保持しており、システムで定義されていない動作である間接参照を試みています。 (5はシステム上の有効なメモリ位置ではありません)

一方、

int nums[] = {1,2,3,4};

numsint型の配列であり、初期化中に渡された要素の数に基づいてメモリが割り当てられると言っている有効な宣言です。

12
Gopi

{5, 2, 1, 4}を割り当てることにより

int *nums = {5, 2, 1, 4};

5をnumsに割り当てます(intからintへのポインターへの暗黙の型キャストの後)。参照を解除すると、0x5のメモリ位置へのアクセス呼び出しが行われます。それはあなたのプログラムがアクセスすることを許可されないかもしれません。

試してみる

printf("%p", (void *)nums);
10
Fahad Siddiqui