「int * nums = {5、2、1、4}」はセグメンテーション違反を引き起こします


81
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をデリファレンスしようとし、セグメンテーション違反を引き起こします。

私が間違っている場合は、私を訂正してください。そして、私が正しければ、配列初期化子がなぜそのように機能するのか理解できないので、詳しく説明してください。



3
すべての警告を有効にしてコンパイルすると、コンパイラが何が起こるかを通知するはずです。
Jabberwocky 2016

1
@GSergそれは重複の近くにはありません。この質問には配列ポインタはありません。その投稿のいくつかの答えはここのものと似ていますが。
Lundin 2016

2
@Lundin私は30%確信していたので、閉じることに投票せず、リンクを投稿しただけでした。
GSerg 2016

3
-pedantic-errorsフラグを付けてGCCを実行する習慣を身に付け、診断を監視します。int *nums = {5, 2, 1, 4};有効なC.ではありません
ANT

回答:


113

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

たとえばint x = {0};、と書くことができます。これは、と完全に同等int x = 0;です。

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

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

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

そしてもちろん、5間接参照を許可されている有効なアドレスではない可能性が高いため、コードはクラッシュして書き込みnums[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 */ };

編集

いくつかの調査の結果、「過剰な要素の初期化子リスト」は実際には有効ではないと言えます。C-これはGCC拡張機能です。

標準の6.7.9初期化は次のように述べています(私の強調):

2 初期化子は、初期化されるエンティティに含まれていないオブジェクトに値を提供しようとしてはなりません。

/-/

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

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

したがって、平易な英語では、「変数を初期化するときは、できるという理由だけで、イニシャライザ式の周りに余分な中括弧を自由に入れてください」と標準で述べられています。


11
で囲まれた単一の値でスカラーオブジェクトを初期化する機能については、「愚かな」ことは何もありません{}。それどころか、{ 0 }ユニバーサルゼロ初期化子として、C言語の最も重要で便利なイディオムの1つを容易にします。Cのすべては、を介してゼロで初期化できます= { 0 }。これは、タイプに依存しないコードを作成するために非常に重要です。
AnT 2016

3
@AnT「ユニバーサルゼロ初期化子」のようなものはありません。集計の場合、{0}単に最初のオブジェクトをゼロに初期化し、残りのオブジェクトを静的なストレージ期間があるかのように初期化することを意味します。私は以来、これはむしろ、いくつかの「ユニバーサル初期化」の意図的な言語設計よりも偶然だと思い{1}1にすべてのオブジェクトを初期化しない
ランディン

3
@Lundin C11 6.5.16.1 / 1カバーp = 5;(整数をポインターに割り当てるためにリストされたケースのいずれも満たされていません); 6.7.9 / 11は、割り当ての制約が初期化にも使用されることを示しています。
MM

4
@Lundin:はい、あります。どのメカニズムがオブジェクトのどの部分を初期化するかはまったく関係ありません。また{}、スカラーの初期化がその目的で特に許可されているかどうかもまったく関係ありません。重要なのは、= { 0 }イニシャライザーがオブジェクト全体ゼロ初期化することが保証されていることだけです。これがまさに、オブジェクトをC言語の古典的で最もエレガントなイディオムの1つにした理由です。
AnT 2016

2
@Lundin:あなたの発言{1}がトピックと何の関係があるのか​​も私には完全に不明確です。これを、アグリゲートのすべてのメンバーのマルチ初期化子として{0}解釈するとは誰も主張していません0
AnT 2016

28

シナリオ1

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

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

あなたはnumsintへのポインタとして宣言しました-それはメモリ内nums1つの整数のアドレスを保持することになっています。

次に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の配列を合法的に割り当てているため、これはセグメンテーション違反ではありません。


シナリオ3

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

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


その他

このようにポインタの値ハードコーディングすると、ほとんどの場合、セグメンテーション違反が発生する運命にあります(どのプロセスがどのメモリ位置にアクセスできるかを判断するのはオペレーティングシステムのタスクであるため)。

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

したがって、経験則では、次のような割り当てられた変数のアドレスへのポインタを常に初期化します。

int a;
int *nums = &a;

または、

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

2
+1それは良いアドバイスですが、多くのプラットフォームの魔法のアドレスを考えると、「決して」は本当に強すぎません。(これらの固定アドレスに定数テーブルを使用することは、現存する変数を指していないため、前述のルールに違反します。)ドライバー開発のような低レベルのものは、そのようなことをかなり頻繁に扱います。
ネイト

3
「これは有効です」-過剰な初期化子を無視することはGCC拡張です。許可されていない標準Cの場合
MM

1
@ TheNate-はい、あなたは正しいです。私はあなたのコメントに基づいて編集しました-ありがとう。
artm 2016

@ MM-それを指摘してくれてありがとう。私はそれを削除するために編集しました。
artm 2016

25

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、アレイの長さを検出するために使用できます。


複合リテラル形式のアレイは、少なくともそれと同じ長さのストレージ寿命を持つことが保証されnumsますか?
スーパーキャット2016

@supercatはい、numsが自動の場合は自動で、numsが静的の場合は静的です
MM

@MM:nums静的変数が関数内で宣言されている場合でも適用されますか、それともコンパイラは、静的変数に割り当てられている場合でも、配列の有効期間を囲んでいるブロックの有効期間に制限する権利がありますか?
スーパーキャット2016

@supercat yes(最初のビット)。2番目のオプションは、関数が2回目に呼び出されたときのUBを意味します(静的変数は最初の呼び出しでのみ初期化されるため)
MM

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

numsタイプのポインタですint。したがって、このポイントを有効なメモリ位置にする必要があります。num[0]ランダムなメモリ位置を逆参照しようとしているため、セグメンテーション違反が発生します。

はい、ポインタは値5を保持しており、システムでの未定義の動作である値を逆参照しようとしています。(5システム上の有効なメモリ位置ではないようです)

一方、

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

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


1
「はい、ポインタは値5を保持しており、未定義の動作である値を逆参照しようとしています。」まったくそうではありませんが、それは完全に細かく、明確に定義された動作です。しかし、OPが使用しているシステムでは、それは有効なメモリアドレスではないため、クラッシュします。
Lundin 2016

@Lundin同意します。しかし、OPは5が有効なメモリ位置であることを知らなかったと思うので、私はそれらの行で話しました。編集がお役に立てば
幸いです

このようにすべきですか?int *nums = (int[]){5, 2, 1, 4};
イスラムアザブ2016

10

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

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

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

試してみてください

printf("%p", (void *)nums);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.