web-dev-qa-db-ja.com

C複合リテラル、配列へのポインタ

複合リテラルを変数に割り当てようとしていますが、機能しないようです。以下を参照してください。

  int *p[] = (int *[]) {{1,2,3},{4,5,6}};

Gccでエラーが発生しました。

しかし、私がこれだけを書くと:

  int p[] = (int []) {1,2,3,4,5,6};

それなら大丈夫です.

しかし、私が望むものではありません。

エラーが発生する理由がわかりません。配列のように初期化するか、charsの配列のポインターと一緒に使用すると、問題ありません。以下を参照してください。

  int *p[] = (int *[]) {{1,2,3},{4,5,6}}; //I got a error
  int p[][3] = {{1,2,3},{4,5,6}}; //it's okay
  char *p[] = (char *[]) {"one", "two"...}; // it's okay!

注最初の形式でエラーが発生した理由がわかりません。できません。または、複合リテラルである必要があるため、2番目の形式のように記述したくありません。コンパイラにとって配列の大きさを言うために。 2番目のようなものが必要ですが、int値用です。

前もって感謝します。

31
drigoSkalWalker

まず、キャストはすべての例で冗長であり、削除できます。次に、多次元配列を初期化するための構文を使用しています。これには、メモリのシーケンシャルブロックを割り当てるために2番目の次元を定義する必要があります。代わりに、以下の2つのアプローチのいずれかを試してください。

  • 多次元配列:

    int p[][3] = {{1,2,3},{4,5,6}};
    
  • 1次元配列へのポインターの配列:

    int p1[] = {1,2,3};
    int p2[] = {4,5,6};
    int *p[] = {p1,p2};
    

後者の方法には、さまざまな長さのサブアレイを使用できるという利点があります。一方、前者の方法では、メモリが連続して配置されます。

使用しないことを強くお勧めするもう1つのアプローチは、整数を文字列リテラルでエンコードすることです。これは移植性のないハックです。また、文字列リテラルのデータは一定であると想定されています。配列は可変である必要がありますか?

int *p[] = (int *[]) {
    "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00",
    "\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00"
};

この例は32ビットのリトルエンディアンマシンで機能する可能性がありますが、iPadから入力しているため、現時点では確認できません。繰り返しますが、それは使用しないでください。育てても汚いです。

あなたが発見したキャスト方法は、ポインターへのポインターでも機能するようです。多次元配列のようにインデックスを付けることもできます。

int **p = (int *[]) { (int[]) {1,2,3}, (int[]) {4,5,6} };
31
Judge Maygarden

まず、"配列はポインタではありません"。であることを理解してください。

int p[] = (int []) {1,2,3,4,5,6};

上記の場合、pは整数の配列です。要素のコピー{1,2,3,4,5,6}からp。ここでは型キャストは必要なく、rvalueタイプとlvalueタイプの両方が一致します。これは整数配列であるため、エラーは発生しません。

int *p[] = (int *[]) {{1,2,3},{4,5,6}};

「最初のエラーが発生した理由がわかりません。.」

上記の場合、pは整数ポインタの配列です。しかし {{1,2,3},{4,5,6}}は2次元配列(つまり、[] [])であり、ポインターの配列に型キャストすることはできません。次のように初期化する必要があります-

int p[][3] = { {1,2,3},{4,5,6} };
  // ^^ First index of array is optional because with each column having 3 elements
  // it is obvious that array has two rows which compiler can figure out.

しかし、なぜこのステートメントはコンパイルされたのですか?

char *p[] = {"one", "two"...};

文字列リテラルは整数リテラルとは異なります。この場合も、pは文字ポインタの配列です。実際に言ったとき"one"配列にコピーするか、読み取り専用と見なしてその場所を指すことができます

char cpy[] = "one" ;
cpy[0] = 't' ;  // Not a problem

char *readOnly = "one" ;
readOnly[0] = 't' ;  // Error because of copy of it is not made but pointing
                     // to a read only location.

文字列リテラルでは、上記のいずれの場合も可能です。だから、それがステートメントがコンパイルされた理由です。だが -

char *p[] = {"one", "two"...}; // All the string literals are stored in 
                               // read only locations and at each of the array index 
                               // stores the starting index of each string literal.

コンパイラにとって配列の大きさは言いたくありません。

mallocを使用してメモリを動的に割り当てることが解決策です。

それが役に立てば幸い !

13
Mahesh

誰も言っていないので:2D配列へのポインタが必要な場合は、(おそらく)次のようなことを行うことができます。

_int (*p)[][3] = &(int[][3]) {{1,2,3},{4,5,6}};
_

EDIT:または、を介して最初の要素へのポインタを持つことができます

_int (*p)[3] = (int[][3]) {{1,2,3},{4,5,6}};
_

例が機能しない理由は、_{{1,2,3},{4,5,6}}_がタイプ_int*[]_の有効な初期化子ではないためです(_{1,2,3}_は_int*_の有効な初期化子ではないため)。 not_int[2][3]_ではないことに注意してください—これは単に無効な式です。

文字列に対して機能する理由は、_"one"_が_char[]_および_char[N]_(一部のN> 3の場合)の有効な初期化子であるためです。 expressionとして、それはapproximately _(const char[]){'o','n','e','\0'}_と同等ですが、コンパイラが定数を失ってもあまり文句を言わない点が異なります。

はい、初期化子と式には大きな違いがあります。 char s[] = (char[]){3,2,1,0};はC99(およびおそらくC++ pre-0x)のコンパイルエラーであると確信しています。他にもたくさんありますが、_T foo = ...;_は変数の初期化であり、似ているように見えますが、代入ではありません。 (代入演算子が呼び出されないため、C++では特に異なります。)

そして、ポインタとの混同の理由:

  • タイプ_T[]_は、必要に応じて暗黙的にタイプ_T*_(最初の要素へのポインター)に変換されます。
  • 関数の引数リストの_T arg1[]_は、実際には_T * arg1_を意味します。さまざまな理由で関数に配列を渡すことはできません。それは不可能。試してみると、実際には配列へのポインタを渡しています。 (ただし、固定サイズの配列を含む構造体を関数に渡すことはできます。)
  • それらは両方とも、同一の(私が思うに)セマンティクスで逆参照および添え字を付けることができます。

EDIT:観察者は、私の最初の例が構文的に_int * p = &1;_とほぼ同等であることに気付くかもしれませんが、これは無効です。これはC99で機能します。これは、関数内の複合リテラルが「囲んでいるブロックに関連付けられた自動ストレージ期間を持っている」ためです( ISO/IEC 9899:TC )。

5
tc.

ポインタと配列を混同しているようです。それらは同じものではありません!配列はリスト自体であり、ポインタは単なるアドレスです。次に、ポインタ演算を使用すると、ポインタが配列であると偽ることができます。配列の名前が最初の要素へのポインタであるという事実により、すべてが混乱してまとめられます。 ;)

int *p[] = (int *[]) {{1,2,3},{4,5,6}};      //I got a error

ここで、pはポインタの配列であるため、アドレスが1、2、3の要素を最初の配列に割り当て、4、5、6を2番目の配列に割り当てようとしています。これらのメモリ位置にアクセスできないため、セグメンテーション違反が発生します。

int p[][3] = {{1,2,3},{4,5,6}};              //it's okay

これは配列の配列であるため、これは問題ありません。したがって、今回は1、2、3、4、5、および6はアドレスではなく、elements自体です。

char *p[] = (char *[]) {"one", "two"...};    // it's okay!

文字列リテラル( "one"、 "two"、...)は実際には文字列ではなく、それらの文字列へのポインタであるため、これは問題ありません。したがって、文字列リテラル "one"のアドレスをp [1]に割り当てます。 。
ところで、これはchar abc[]; abc = "abc";を実行するのと同じです。 char *def; def = "def";が問題を解決している間は、配列にポインタを割り当てることができないため、これはコンパイルされません。

1
BlackBear

使用しているのはintポインタの配列です。配列へのポインタを使用する必要があります:

int (*p)[] = (int *) {{1,2,3}, {4,5,6}}

詳細については、 this の回答をご覧ください。

1
Karan