web-dev-qa-db-ja.com

char * str = {"foo"、...}とchar str [] [5] = {"foo"、...}配列定義の違いは何ですか?

ケース1:私が書くとき

char*str={"what","is","this"};

str[i]="newstring";は有効ですが、str[i][j]='j'; 無効です。

ケース2:書くとき

char str[][5]={"what","is","this"};

str[i]="newstring";は無効ですが、str[i][j]='J';は有効です。

なぜそうですか?私は初心者です。他の答えを読んだ後、すでに非常に混乱しています。

30
Ashish Dogra

まず第一に:提案:配列はポインターではなく、その逆も !!

とはいえ、この特定のシナリオを理解するために、

  • 最初の場合

    char*str={"what","is","this"};
    

    あなたが思っていることはしません。これは制約違反であり、§6.7.9/ P2の章に従って、準拠するC実装からの診断が必要です。

    イニシャライザは、初期化されているエンティティに含まれていないオブジェクトの値を提供しようとしません。

    警告を有効にすると、(少なくとも)を参照してください

    警告:スカラー初期化子の余分な要素

      char*str={"what","is","this"};
    

    ただし、a(ny)厳密な適合が有効になっているコンパイラは、コードのコンパイルを拒否する必要があります。コンパイラは、とにかくバイナリをコンパイルして生成することを選択しました。 C言語の定義は、コンパイラの実装次第です(したがって、大きく異なる場合があります)。

    この場合、コンパイラーは、このステートメントを機能的にchar*str= "what";と同じだけにすることを決定しました

    したがって、ここでstrcharへのポインターであり、string literalを指します。ポインタに再割り当てできますが、

    str="newstring";  //this is valid
    

    しかし、のような声明

     str[i]="newstring";
    

    invalidになります。ここでは、ポインタ型はchar型に変換されて保存されますが、型には互換性がありません。この場合、コンパイラは無効な変換に関する警告をスローする必要があります。

    その後、次のような文

    str[i][j]='J'; // compiler error
    

    配列の添え字[]演算子を「オブジェクトタイプ全体へのポインタ」ではないものに使用しているため、構文的に無効です。

    str[i][j] = ...
          ^^^------------------- cannot use this
    ^^^^^^ --------------------- str[i] is of type 'char', 
                                 not a pointer to be used as the operand for [] operator.
    
  • 一方、2番目のケースでは

    strは配列の配列です。個々の配列要素を変更できますが、

     str[i][j]='J'; // change individual element, good to go.
    

    ただし、配列に割り当てることはできません。

     str[i]="newstring";  // nopes, array type is not an lvalue!!
    

  • 最後に、(コメントに見られるように)あなたが書くつもりであると考えてください

    char* str[ ] ={"what","is","this"};
    

    最初のケースでは、配列の同じロジックが保持されます。これにより、strがポインターの配列になります。配列メンバーは割り当て可能ですので、

    str[i]="newstring";  // just overwrites the previous pointer
    

    まったく問題ありません。ただし、配列メンバーとして格納されるポインターは、string literalへのポインターです。したがって、上記とまったく同じ理由で、 ndefined behavior を呼び出します。 、文字列リテラルに属するメモリの要素の1つを変更する場合

     str[i][j]='j';   //still invalid, as above.
    
26
Sourav Ghosh

メモリレイアウトが異なります。

char* str[] = {"what", "is", "this"};

    str
+--------+      +-----+
| pointer| ---> |what0|
+--------+      +-----+   +---+
| pointer| -------------> |is0|
+--------+                +---+    +-----+
| pointer| ----------------------> |this0|
+--------+                         +-----+

このメモリレイアウトでは、strは個々の文字列へのポインタの配列です。通常、これらの個々の文字列は静的ストレージに常駐するため、変更しようとするとエラーになります。図では、0は、終端のヌルバイトを示します。

char str[][5] = {"what", "is", "this"};

  str
+-----+
|what0|
+-----+
|is000|
+-----+
|this0|
+-----+

この場合、strは、スタック上にある連続した2D文字の配列です。配列が初期化されると、文字列はこのメモリ領域にコピーされ、個々の文字列にはゼロバイトが埋め込まれ、配列に規則的な形状が与えられます。

これら2つのメモリレイアウトは、基本的に互いに互換性がありません。他方へのポインターを予期する関数に、いずれかを渡すことはできません。ただし、個々の文字列へのアクセスには互換性があります。 str[1]、あなたはchar*バイトを含むメモリ領域の最初の文字is0、つまりC文字列。

最初のケースでは、このポインタがメモリから単にロードされることは明らかです。 2番目の場合、ポインターはarray-pointer-decayを介して作成されます:str[1]は実際には正確に5バイトの配列を示します(is000)、ほとんどすべてのコンテキストで最初の要素へのポインタにすぐに減衰します。ただし、array-pointer-decayの完全な説明は、この答えの範囲を超えていると思います。興味がある場合は、Google array-pointer-decay。

16
cmaster

1つ目では、charへのポインターである変数を定義します。これは通常、単一の文字列としてのみ使用されます。文字列リテラル"what"を指すようにポインターを初期化します。コンパイラーshouldも、リストに初期化子が多すぎると文句を言います。

2番目の定義は、strを5つのcharの3つの配列の配列にします。つまり、3つの5文字の文字列の配列です。


少し異なって、次のように表示されます。

最初の場合:

 + ----- + + -------- + 
 | str | -> | 「何」| 
 + ----- + + -------- + 

第二にあなたが持っている

 + -------- + -------- + -------- + 
 | 「何」| 「ある」| 「this」| 
 + -------- + -------- + -------- + 

また、最初のバージョンでは、単一の文字列へのポインタを使用して、式str[i] = "newstring"も警告につながるはずであることに注意してください。ポインタをsingle char elementstr[i]

2番目のバージョンでもその割り当ては無効ですが、別の理由により:str[i]array(5つのchar要素)であり、配列に割り当てるのではなく、コピーするだけです。そのため、tryを実行してstrcpy(str[i], "newstring")を実行すると、コンパイラは文句を言いません。ただし、wrongは、10文字をコピーして(ターミネータを覚えて)5文字の配列にしようとするため、未定義の動作

  • 最初の宣言で

    char *str={"what","is","this"}; 
    

    strcharへのポインターとして宣言し、スカラーです。標準では

    6.7.9初期化(p11):

    スカラーの初期化子は、単一の式で、オプションで中括弧で囲まれます。 [...]

    つまり、スカラー型は括弧で囲まれた初期化子を持つことができますが、単一の式を持ちますが、

    char *str = {"what","is","this"}; // three expressions in brace enclosed initializer
    

    これをどのように処理するかはコンパイラ次第です。初期化子の残りの部分で発生するのは bug であることに注意してください。確認コンパイラーは、診断メッセージを提供する必要があります。

    [Warning] excess elements in scalar initializer   
    

    5.1.1.3診断(P1):

    動作が明示的に未定義または実装として指定されている場合でも、前処理の翻訳単位または翻訳単位に構文規則または制約の違反が含まれる場合、適合実装は少なくとも1つの診断メッセージ(実装定義の方法で識別)を生成するものとします。定義済み

  • str[i]="newstring";は有効ですが、str[i][j]='j';は無効です。

    str[i]char型であり、charデータ型のみを保持できます。 "newstring"char *の)の割り当ては無効です。添字演算子は配列またはポインターのデータ型にのみ適用できるため、ステートメントstr[i][j]='j';は無効です。

  • str[i]="newstring";の配列としてstrを宣言することにより、char *を機能させることができます。

    char *str[] = {"what","is","this"};
    

    この場合、str[i]char *型であり、文字列リテラルを割り当てることができますが、文字列リテラルstr[i]を変更すると、未定義の動作が呼び出されます。それはあなたがstr[0][0] = 'W'を行うことができないと言った。

  • スニペット

    char str[][5]={"what","is","this"};
    

    strの配列の配列としてcharを宣言します。 str[i]は実際には配列であり、配列は変更不可能な左辺値であるため、代入演算子の左オペランドとして使用することはできません。これにより、str[i]="newstring";が無効になります。一方、str[i][j]='J';は、配列の要素を変更できるため機能します。

2
haccks

あなたが他の答えが私を混乱させると言ったという理由だけで、最初に簡単な例で何が起こっているかを見てみましょう

_char *ptr = "somestring";
_

ここで_"somestring"_は文字列リテラルで、メモリの読み取り専用データセクションに保存されます。 ptrは、割り当てられたメモリの最初のバイトを指すポインター(コードの同じセクションの他の変数と同じように割り当てられます)です。

したがって、これらの2つのステートメントを確認します

_char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a';     //statement 2 error
_

ステートメント1は完全に有効な操作を実行しています(1つのポインターを別のポインターに割り当てています)が、ステートメント2は有効な操作ではありません(読み取り専用の場所への書き込みを試行しています)。

一方、次のように記述した場合:

_char ptr[] = "somestring";
_

ここで、ptrは実際にはポインタではなく、配列の名前です(ポインタとは異なり、メモリ内に余分なスペースは必要ありません)。 _"somestring"_(読み取り専用ではない)が要求するのと同じバイト数を割り当て、それだけです。

したがって、同じ2つのステートメントと1つの追加ステートメントを検討してください

_char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a';     //statement 2 OK
ptr = "someotherstring" //statement 3 error
_

ステートメント1は完全に有効な操作を実行しています(配列名をポインターに割り当て、配列名は最初のバイトのアドレスを返します)。また、ステートメント2もメモリが読み取り専用ではないため有効です。

ステートメント3は、ここでptrはポインターではないため、有効な操作ではありません。他のメモリー位置を指すことはできません。


このコードでは、

_char **str={"what","is","this"};
_

_*str_はポインターです(_str[i]_は*(str+i)と同じです)

しかし、このコードでは

_char str[][] = {"what", "is", "this"};
_

_str[i]_はポインターではありません。これは配列の名前です。

上記と同じことが続きます。

1
Raman

事例1:

書くとき

char*str={"what","is","this"};

str[i]="newstring";は有効ですが、str[i][j]='j';は無効です。

パートI.I
>> char*str={"what","is","this"};

このステートメントでは、strchar型へのポインターです。コンパイル時には、このステートメントで警告メッセージを取得する必要があります。

warning: excess elements in scalar initializer
        char*str={"what","is","this"};
                         ^

警告の理由は次のとおりです-スカラーに複数の初期化子を提供しています。
[算術型とポインター型は、まとめてスカラー型と呼ばれます。

strはスカラーであり、 C Standards#6.7.9p11 から:

スカラーの初期化子は、オプションで中括弧で囲まれた単一の式でなければなりません。 ..

さらに、スカラーに複数の初期化子を与えることは 未定義の動作 です。
From C Standards#J.2 Undefined behavior

スカラーの初期化子は、単一の式でも、括弧で囲まれた単一の式でもありません

これは標準に従って未定義の動作であるため、さらに議論する意味はありませんパートI.IIおよびパートI.IIIについて、char *str="somestring"型の理解を深めるためだけに、char *を仮定して説明します。
文字列へのポインタの配列を作成するようです。両方のケースについて説明した後、この投稿の下に、文字列へのポインタの配列について簡単な説明を追加しました。

パートI.II
>> then str[i]="newstring"; is valid

いいえ、これは無効です
繰り返しますが、互換性のない変換のため、コンパイラーはこのステートメントで警告メッセージを提供する必要があります。
strchar型へのポインターであるため。したがって、str[i]は、i [str[i] --> *(str + i)]が指すオブジェクトの後ろのstr位置の文字です。

"newstring"は文字列リテラルであり、char *型の配列の初期化に使用される場合を除き、文字列リテラルはポインターに減衰します。ここでは、char型に割り当てようとしています。したがって、コンパイラはそれを警告として報告します。

パートI.III
>> whereas str[i][j]='j'; is invalid.

はい、これは無効です。
[](添字演算子)は、配列またはポインターオペランドで使用できます。
str[i]は文字であり、str[i][j]は、無効なcharオペランドで[]を使用していることを意味します。したがって、コンパイラはそれをエラーとして報告します。

事例2:

書くとき

char str[][5]={"what","is","this"};

str[i]="newstring";は無効ですが、str[i][j]='J';は有効です。

パートII.I
>> char str[][5]={"what","is","this"};

これは絶対に正しいです。ここで、strは2D配列です。初期化子の数に基づいて、コンパイラは最初の次元を自動的に設定します。この場合、str[][5]のメモリ内ビューは次のようになります。

         str
         +-+-+-+-+-+
  str[0] |w|h|a|t|0|
         +-+-+-+-+-+
  str[1] |i|s|0|0|0|
         +-+-+-+-+-+
  str[2] |t|h|i|s|0|
         +-+-+-+-+-+

初期化リストに基づいて、2D配列の各要素が初期化され、残りの要素は0に設定されます。

パートII.II
>> then str[i]="newstring"; is not valid

はい、これは無効です。
str[i]は1次元配列です。
C標準に従って、配列は変更可能な左辺値ではありません。
From C Standards#6.3.2.1p1

左辺値は、潜在的にオブジェクトを指定する式(void以外のオブジェクトタイプ)です; 64)左辺値が評価時にオブジェクトを指定しない場合、動作は未定義です。オブジェクトが特定のタイプを持つと言われるとき、タイプはオブジェクトを指定するために使用される左辺値によって指定されます。変更可能な左辺値は、配列型を持たず、不完全な型を持たず、const修飾型を持たない左辺値であり、構造体または共用体の場合、メンバー(再帰的に、メンバーを含む)を持たないまたは含まれるすべての集合体または共用体の要素)、const修飾型。

また、配列名は、sizeof演算子、_Alignof演算子、または単項&演算子のオペランドである場合を除き、配列オブジェクトの初期要素を指すポインターに変換されます。

C Standards#6.3.2.1p から:

Sizeof演算子、_Alignof演算子、または単項&演算子のオペランドである場合、または配列を初期化するために使用される文字列リテラルである場合を除き、タイプ '' array of type ''を持つ式は、配列オブジェクトの初期要素を指し、左辺値ではない「型へのポインタ」を入力します。

strはすでに初期化されているため、他の文字列リテラルをiに割り当てると番目 str配列の左辺値とchar *型の右辺値があるため、文字列リテラルが割り当てに互換性のないポインターに変換されるcharの配列。したがって、コンパイラはそれをエラーとして報告します。

パートII.III
>> whereas str[i][j]='J'; is valid.

はい、これはijが与えられた配列strに対して有効な値である限り有効です。

str[i][j]char型であるため、文字を割り当てることができます。 Cは配列の境界をチェックせず、境界外の配列へのアクセスは未定義の動作を含みます-プログラマーが意図したとおりに正確に実行するか、セグメンテーションエラーが発生したり、誤って誤った結果を生成したりする可能性があります。


ケース1で、文字列へのポインタの配列を作成するとします。
次のようになります。

char *str[]={"what","is","this"};
         ^^

strのメモリ内ビューは次のようになります。

      str
        +----+    +-+-+-+-+--+
  str[0]|    |--->|w|h|a|t|\0|
        |    |    +-+-+-+-+--+
        +----+    +-+-+--+
  str[1]|    |--->|i|s|\0|
        |    |    +-+-+--+
        +----+    +-+-+-+-+--+
  str[2]|    |--->|t|h|i|s|\0|
        |    |    +-+-+-+-+--+
        +----+

"what""is"、および"this"は文字列リテラルです。
str[0]str[1]、およびstr[2]は、それぞれの文字列リテラルへのポインターであり、他の文字列を指すようにすることもできます。

したがって、これはまったく問題ありません。

str[i]="newstring"; 

iが1であると仮定すると、str[1]ポインターは文字列リテラル"newstring"を指すようになります。

        +----+    +-+-+-+-+-+-+-+-+-+--+
  str[1]|    |--->|n|e|w|s|t|r|i|n|g|\0|
        |    |    +-+-+-+-+-+-+-+-+-+--+
        +----+

しかし、これを行うべきではありません

str[i][j]='j';

i=1およびj=0を想定しているため、str[i][j]は2番目の文字列の最初の文字です)

標準に従って、文字列リテラルを変更しようとすると、それらは読み取り専用ストレージに保存されるか、他の文字列リテラルと結合されるため、未定義の動作になります。

C standard#6.4.5p7 から:

要素に適切な値がある場合、これらの配列が個別であるかどうかは指定されていません。プログラムがそのような配列を変更しようとした場合、動作は未定義です。


追加:

C言語にはネイティブの文字列型はありません。 C言語では、文字列は nullで終わる配列 文字です。配列とポインターの違いを知っておく必要があります。

配列、ポインター、配列の初期化について理解を深めるために、以下を読むことをお勧めします。

  1. 配列の初期化、 this を確認します。
  2. ポインタと配列の等価性は、 this および this を確認してください。
0
H.S.
  • はじめに

    char*str={"what","is","this"};
    

    は有効なCコードでもない 1)、それについて議論することはあまり意味がありません。なんらかの理由で、gccコンパイラーはこのコードを警告のみで通過させます。コンパイラの警告を無視しないでください。 gccを使用する場合は、必ず-std=c11 -pedantic-errors -Wall -Wextraを使用してコンパイルしてください。

  • この非標準コードに遭遇したときにgccが行うことは、char*str={"what"};を記述したかのように扱うことです。これはchar*str="what";と同じものです。これは、C言語によって保証されるものではありません。

  • str[i][j]は、間接レベルが1レベルしかない場合でも、ポインターを2回間接しようとするため、コンパイラエラーが発生します。タイピングと同じくらい意味がありません

    int array [3] = {1,2,3}; int x = array[0][0];

  • char* str = ...char str[] = ...の違いについては、 FAQ:char s []とchar * s?の違いは何ですか? を参照してください。

  • char str[][5]={"what","is","this"};ケースに関しては、配列の配列(2D配列)を作成します。最も内側の次元は5に設定され、最も外側の次元は、プログラマーが提供したイニシャライザーの数に応じてコンパイラーによって自動的に設定されます。このケース3では、コードはchar[3][5]と同等です。

  • str[i]は、配列の配列の配列番号iを提供します。 Cの配列に割り当てることはできません。これが言語の設計方法だからです。さらに、とにかく文字列に対してそうするのは間違っているでしょう FAQ:新しい文字列値を正しく割り当てる方法?


1) これは、C11 6.7.9/2の制約違反です。 6.7.9/11も参照してください。

0
Lundin