• ベストアンサー

C言語の基礎的な質問---文字配列の初期化

C言語の配列の初期化に関する質問です。 もし規格によって回答が異なる場合は、ANSIのCということにしてください。 関数の中に、 char str[ ]="ABC"; (イ) という宣言があるとします。(staticは付きません。) これは、 char str[ ]={'A', 'B', 'C', '\0'}; (ロ) と全く同じ意味でしょうか。  似て非なるものに char *str="ABC"; (ハ) というものがあります。この場合は、 strとは違うところに"ABC"('C'の次には'\0'があります。)という領域が確保されていて、 その先頭アドレスでstrが初期化されるのですよね。 (イ)(ロ)(ハ)のいずれの場合も関数の中に書かれているとすれば、 いずれもstrは自動変数で、関数実行時にstrの領域が確保されますよね。 (イ)は配列strの領域が確保されるときに、 配列strとは別のところにある"ABC"という領域の内容を、コピーして設定する、 ということでしょうか。 (ロ)は、配列の領域確保時にstr[0]を'A'で、str[1]を'B'で、str[2]を'C'で、str[3]を'\0'で、初期化する、 ということで、 配列とは別のところには"ABC"という領域はない、 という考えでよろしいでしょうか。 もしそうだとしたら、配列とは別のところに"ABC"という領域があるかどうかという点で(イ)と(ロ)は異なることになりますが、そう考えてよろしいのでしょうか。 それとも、そういうことは処理系に依存することなんでしょうか。

noname#1357
noname#1357

質問者が選んだベストアンサー

  • ベストアンサー
  • terra5
  • ベストアンサー率34% (574/1662)
回答No.3

>この記述は、配列とは別のところに初期値で埋まった領域(質問でいう"ABC")がある、という意味に思えてしまいます。 実行のする度に値が設定されるわけですから、配列と同じ並び順でない可能性はあっても、必ずなんらかの形でどこかに存在します。 >これも、文字列定数の領域(この場合は"ABCD")が配列の領域とは別にあるように読めてしまいます。 これもその通りでしょう。 >配列strとは別のところにある"ABC"という領域の内容を、コピーして設定する、 >ということでしょうか。 基本的にはその通りでしょう。 やろうと思えば"A B C "というところから一つおきにコピーする実装も可能でしょうけど(笑) また、(イ)と(ロ)はコンパイラ上では同じ扱いだったと思いますし、kaicheさんが書いたやり方は、実質的にコピーと同じですね。 初期化のために代入する値が結局はどこかに必要ですから。 ただ、"ABC"と連続して格納されている領域があるかないかは、実装よるでしょうから、 どちらの場合も連続領域におかないようにするコンパイラの作成は可能でしょう。 ただ、通常は効率上そういう実装はまず無いと思いますが。 ただ、この違いも使う上ではどうでもいいことですけど。 あと、気になるのは、領域というのをどういう意味で考えているかどうかですね. もし、私が考えているものより、狭い意味で使っているなら、説明も変えないといけないでしょう。 あ、私もコンパイラの実装まではやったことはありませんが、Cのコンパイル結果をアセンブラレベルで見たこととか,OSのメモリ管理とかの知識はそれなりにあります。

noname#1357
質問者

補足

ということはこういうことですね。 (文字配列の初期化に関して) 配列とは別のところに初期化子があり、配列の領域を確保するときにそれをコピーしている。 初期化子がどこかになければ配列を初期化することは出来ない。 しかし、それは char *str="ABC"; (ハ) の"ABC"とは異なり、初期化子がどう並んでいるかはわからない。 どう並んでいてもそれを直接触ることは出来ないので関係ない。 つまり >この違いも使う上ではどうでもいいことですけど。 ということですね。 char str[ ]="ABC"; (イ) と char str[ ]={'A', 'B', 'C', '\0'}; (ロ) はまったく同じ意味だ。 他方、(ハ)は'A', 'B', 'C'がどこかにあるという点では同じでも、 ぜんぜん意味合いが異なる。

その他の回答 (3)

  • terra5
  • ベストアンサー率34% (574/1662)
回答No.4

>ということはこういうことですね。 はい。 あとちょっと気になったのは, >strとは違うところに"ABC"('C'の次には'\0'がありま す。)という領域が確保されていて、 の領域ですが、ここは定数領域であって変数の領域では無く、 書き換えることはできないということを把握しているかです。 確か,ANSIの規格では書き換えできないとなっていたと 思います。 ただ、古いコンパイラやOSによっては、書き換え可能な4byteの領域として扱えたケースもありました。 で、動いてしまったので間違って覚えると(^^;; それと本の記述に戻りますが, >配列の領域の確保および初期化が行われることになるので、非常にムダがあります(まず文字列定数 ですが、内容が不変の場合はそうですが、 ケースによってはそうすることが必要な場合もありますから、 一概に無駄というのは正確ではありません。 もしかすると、引用されない部分に記述があるのかも知れませんが。 あと、メジャーではないと言い切っている辺りも気になります。 その本の内容は少し注意したほうがいいかも知れません。

noname#1357
質問者

お礼

ご回答ありがとうございます。 char *str="ABC"; (ハ) についてですが、、 >>strとは違うところに"ABC"('C'の次には'\0'がありま >す。)という領域が確保されていて、 >の領域ですが、ここは定数領域であって変数の領域では無く、 >書き換えることはできないということを把握しているかです。 >確か,ANSIの規格では書き換えできないとなっていたと >思います。 この件については、今 私の手元にある書籍ですと、以下に書かれています。 #本の名前を挙げたところで、同じ本を持っていない人にとっては意味ないですが( ;^^)ヘ.. 『プログラミング言語C 第2版(訳書訂正版)』p.236 『新ANSI C言語辞典』(平林雅英著、技術評論社)の"文字列リテラル"の項 『C言語プログラミングの落とし穴』(柴田望洋著、ソフトバンク)p.77 あと、本の記述に関することはナルホドそうだと私も思います。

  • hitomura
  • ベストアンサー率48% (325/664)
回答No.2

(No.1の回答に対する補足を読んで) 了解、そういった背景があったのですね。 初期化するためのデータをあらかじめどこかに持っているのでは?と言う話ですね。 …と、なると、処理系に依存すると思います。 ただし、その元データに対してどうこうする事は出来ません。 writeはもちろんのこと、readもです。 …って、これはあまり自信がないなぁ…コンパイラの実装なんてやったことないから…

noname#1357
質問者

補足

(文字配列の初期化に関して) >ただし、その元データに対してどうこうする事は出来ません。 なるほど、つまり、たとえ初期化の元のデータが配列と別のところにあろうと さわることはできないのだから、 結局 無いのといっしょだ、ということですね。

  • hitomura
  • ベストアンサー率48% (325/664)
回答No.1

>関数の中に、 >char str[ ]="ABC"; (イ) >という宣言があるとします。(staticは付きません。) >これは、 >char str[ ]={'A', 'B', 'C', '\0'}; (ロ) >と全く同じ意味でしょうか。 はい、そのとおりです。 >似て非なるものに >char *str="ABC"; (ハ) >というものがあります。この場合は、 >strとは違うところに"ABC"('C'の次には'\0'があります。)という領域が >確保されていて、 >その先頭アドレスでstrが初期化されるのですよね。 これもそのとおりです。 >(イ)(ロ)(ハ)のいずれの場合も関数の中に書かれているとすれば、 >いずれもstrは自動変数で、関数実行時にstrの領域が確保されますよね。 (ハ)の場合はそのとおりですが、(イ)(ロ)の場合はちがいます。 確保されるはchar配列領域です。strはそのアドレスを指す定数と考えたほうがいいでしょう(この辺ちょっと自信なし)。 >(イ)は配列strの領域が確保されるときに、 >配列strとは別のところにある"ABC"という領域の内容を、コピーして設定する、 >ということでしょうか。 > >(ロ)は、配列の領域確保時にstr[0]を'A'で、str[1]を'B'で、str[2] >を'C'で、str[3]を'\0'で、初期化する、 >ということで、 >配列とは別のところには"ABC"という領域はない、 >という考えでよろしいでしょうか。 どちらも、後者の方法で初期化が行われます。したがって、どちらも配列とは別のところには"ABC"という領域はありません。 「もしそうなら…」以降は仮定が成り立たないので回答を省略します。

noname#1357
質問者

お礼

(この質問を締め切る前に) No.1の補足に、 >はっきり言って、これらは誤りですよね。 なる記述がありますが、基本的にはこれらの本の記述は誤りではないようです。 (ただし、柴田望洋氏の本について、注意して読んだほうがよいとのご指摘が回答No.4にあります。)

noname#1357
質問者

補足

(ご回答No.1を拝見した上で書きます。) ご回答有難うございます。(イ)と(ロ)について、 >どちらも、後者の方法で初期化が行われます。したがって、どちらも配列とは別のところには"ABC"という領域はありません。 明快な答えで、よくわかりました。 でも、私、いろいろ本で調べたんですが、アレレと思う記述を見つけてしまいました。 (人が書いた本まで聞かれても困る、と思われるかもしれませんが。) 「ANSI Cでも、集成体型の初期化子には、定数しか書けないという制限があります。 (中略)  集成体の初期化子に定数しか書けないことにすることで、コンパイラをちょっとばかり簡単にすることができます。あらかじめ初期値で埋めた領域をどこかに作っておいて、ブロックに突入した時点でスタックにコピーすればよいからです。」(※1) (同じページに、「実行中に、配列などの集成体型を初期化しようと思ったら」という記述があるので、集成体型には「配列」も含まれると思います。)  この記述は、配列とは別のところに初期値で埋まった領域(質問でいう"ABC")がある、という意味に思えてしまいます。 また別の本には、こんな記述があります。 「K&Rでは、自動的な配列を初期化することは不可能でした。ですから、関数の中で  char str[ ] = "ABCD"; (1) という定義はできませんでした。 (中略)  (1)はANSIで認められた新しい方法であり、あまりメジャーではありません。実際、関数が呼び出されるたびに、配列の領域の確保および初期化が行われることになるので、非常にムダがあります(まず文字列定数"ABCD"という領域がどこかに取られます。さらに関数が呼び出されるたびに、これとは別にstrという配列の領域が確保され、その各要素が 'A', 'B', 'C', 'D', '\0' で初期化されることになります)。」(※2) これも、文字列定数の領域(この場合は"ABCD")が配列の領域とは別にあるように読めてしまいます。 はっきり言って、これらは誤りですよね。 (※1)『C言語ポインタ完全制覇』(前橋和弥著、技術評論社)p.113 (※2)『新版秘伝C言語問答ポインタ編』(柴田望洋著、ソフトバンク パブリッシング)p.63

関連するQ&A

  • C言語の配列について質問です。

    配列の添え字に別の配列を指定するような以下の処理について 質問です。 #include <stdio.h> int main(void) {    int k = 0;    int kn = 0;    char str[256];    char key[] = "ABCD";    kn = strlen(key);    for(k=0; k<kn-1; k++) {    str[key[k]] = kn-k-1;    } } 上記についてですが、for文の中で "str[key[k]]"とありますが、 ループ処理にともなって配列str[] の添え字は、str[A], str[B], str[C] となるのでしょうか?? その場合、結果的に配列str[]の各要素にはどのような値が格納され るのでしょうか? そもそも、配列の添え字に対して別の配列を指定した場合の動きが よくわかりません。 どなたか教えて頂けますか??

  • C言語のint型の配列が分かりません

    #include<stdio.h> int main(void) { int str[ ]={0,1,2} printf("%s\n", str); return 0; } というプログラムをC言語でつくってみましたが動きません.(012と表示されて欲しかったのですが) int str[ ]={1,2,3}の部分をchar str[ ]={'0','1','2'}とすれば動きます. そこで質問なのですが, printf("~%s~", (配列名));  はchar型の配列にしか適応できないのですか? ※追記 puts関数の定義は int puts (const char *str); であるそうなので char型の仮引数にはchar型のアドレスを渡さなければいけません. ではprintf関数の定義は一体どんなものなのですか?

  • C言語の型と配列

    char* str[10]={"a","b"}; char* str2="c"; としたときにstr=str2とすると 型が合わないといったエラーが出ます。 でもstrって結局はポインタの配列の先頭要素のアドレスですよね。 ポインタにポインタを入れているので通るのかなと思ったんですけど、 配列で宣言するとポインタにも型がつくのでしょうか? この例だと strは char * を10個持つ配列をさすポインタ  で、 str2はchar *をさすポインタ みたいなかんじです。 質問の意味がわかりにくいですが、ご指摘をいただければ補足しますので よろしくお願いします。

  • C言語:小文字を大文字に変換する関数を作成

    C言語超初心者です。学校の課題で次のような問題が出されました。 ・問題・ 次に挙げる縛りに沿い、以下の関数とメイン関数を作り、処理結果を画面に作成しなさい。 char *tst(char *str) ・strの中の文字列も小文字を大文字に変換し、変換した文字列が格納されているchar *に返す。 ・引数strの中の文字列は受け取った状態で手を加えない。 ・関数内でmalloc関数を使用し、領域を確保して大文字に変換した文字列を格納しreturnでポインタを返す。 ・malloc関数を使用する。 ---------------------------------------------------------------------------------- 大文字に変換するには while(*str != '\0') { if(*str >= 'a' && *str <= 'z') { *str -= 'a'- 'A'; } ++str; } というのは分かったのですがここから何をすするか全く分かりません。初心者なのでなるべく分かりやすく教えてもらえると有難いです。 お願いします。

  • C言語

    以下のC言語のプログラムを教えてください。 お願いします。 (1)標準入力から文字列(2 文字以上)を入力し,文字数を計上すると共に,入力された文字列の逆順に入れ替える処理を実現してください.なお,以下の要件を満たしたプログラムを作成してください. ・ 入力された文字列は,char 型の配列(要素数50)で受け取ること ・ 文字数を計上するcount 関数(引数:配列のアドレス,戻り値:文字数)を定義 し,main 関数より呼び出すこと ・ 文字列を逆順に入れ替えるreverse 関数(引数:配列のアドレス,戻り値:無し) を定義し,main 関数より呼び出すこと ・ 標準出力の処理は,main 関数で記述すること 【プロトタイプ宣言】 int count(char *str); void reverse(char *str); 【実行結果】 文字列を入力してください(2 文字以上) apple 文字数 = 5 入れ換え前 apple 入れ換え後 elppa (2)char 型の配列(要素数50)を2 つ宣言し,標準入力から2 つの文字列を入力してください.そして,格納した字列を入れ替える関数(swapstr 関数)を作成し,入れ替え前と入れ替え後の配列内の値(文字列)を配列名とともに標準出力するプログラムを作成してください. 【プロトタイプ宣言】 void swapstr(char *str1, char *str2); 【実行結果】 2 つの文字列を入力してください apple strawberry 入れ換え前 配列str1 = apple 配列str2 = strawberry 入れ換え後 配列str1 = strawberry 配列str2 = apple

  • 二次元配列による文字列の配列の受渡しについての質問です。

    二次元配列による文字列の配列の受渡しについての質問です。 #include <stdio.h> void print_pname(char str[][5], int n) { int i, j; for (i = 0; i < n; i++) { printf("str[%d] = \"", i); for (j = 0; str[i][j] != '\0'; j++) putchar(str[i][j]); printf("\"\n"); } } int main(void) { char ary[][5] = {"Lisp", "C", "Ada"}; print_pname(ary, sizeof(ary) / sizeof(ary[0])); return 0; } 上のプログラム中の関数print_pnameの引数char str[][5]についてですが char (*str)[5](配列のポインタ)と変更した場合にwarningが多数発生します。 これはどうしてでしょうか? また、上のプログラムを配列のポインタを使って変更することは可能でしょうか? 以上、よろしくお願いします。

  • C言語の文字列で

    基本的なことですがよろしくお願いします。 文字列の配列を作る時 文字を個々に配列にする場合配列の大きさを\0を合わせた5にすると思います str[5] = {'a','b','c','d','\0'} 疑問に思ったのは文字列で初期化する場合は\0はコンパイル時に自動で付加と説明を受けましたが、初期化時には配列の大きさは文字列の長さだけで良いのでしょうか? str[4] = "abcd" 解らなければ多く取ればいいよと言われればそれまでですが、for文で回転させる時は'\0'はどうなっちゃうんでしょうか

  • 文字列strの中から文字cを探すプログラム(C言語)がわからない

    文字列strの中から文字cを探すプログラム(C言語)がわからない 柴田望洋さんの「[新版]明解C言語」という本の演習11-2なんですがどうしてもわかりません。間違いは無いと思うのにコンパイルすると警告を吐かれます。 僕が書いたプログラムを載せます。 /* 文字列strの中に、文字cが含まれていれば(複数ある場合は、最も先頭側とする)、 その文字へのポインタを返し、含まれていなければNULLを返す関数 char *str_chr(const char *str, int c) {} を作成せよ。 */ #include<stdio.h> char *str_chr(const char *str, int c){ while(*str){ if(*str==c) return str; str++; }     return NULL; } int main(){ char *str; char c; scanf("%s",str); scanf(" %c",c);     printf("%d",str_chr(str,c)); return 0; } コンパイラは「関数str_chrのif分の中のreturn strの型変換に問題がある」と言っているんです。 型変換はしるつもりは無いのにコンパイラはなぜそのように認識するのでしょうか。 またネット答えを探しましたがどうやらこのreturn strの部分はreturn (char*)strが正解のようです。意味がわかりません。strはポインタなのになぜまたわざわざchar型に変換しているのですか?といか(char*)の意味が根本的にわかりません。 質問ばかりですみません。初心者でポインタがどうにも理解できないんです。 誰か詳しい人教えてください。 お願いします。

  • c言語のmalloc関数、またrealloc関数

    c言語のmalloc関数は確保するメモリの領域を、配列としてのみしか処理出来ないのですか。 つまり、malloc関数で確保したメモリの領域を変数、また多次元配列、また構造体としては処理出来ないのでしょうか。 c言語のrealloc関数は以前の確保したメモリの領域から、確保し直したメモリの領域の場所が変わるかもしれないという事ですが、この場合の場所が変わるという意味は、メモリの領域のアドレスが変わるという事でしょうか。 また、以前の確保したメモリの領域に代入していたデータが使用出来なくなるという事でしょうか。

  • C言語 初期化について

    はじめまして。 C言語を学習しております。 【初期化】の意味について混乱しております。 今まで、【初期化】とは下記のような繰り返しのある文に初期値(数え始めの値、下記の例では1)を代入することだと思っていました。 for (i = 1;i <= 10;i++) { printf("メッセージ\n"); } しかし、勉強が進むにつれ、下記のように様々な場面で【初期化】という言葉が出てきたことで、【初期化】の意味がわからなくなりました。 ●【文字変数の配列の初期化】の例 char str[6] = {'M','A','R','I','O'}; ●文字列リテラルを用いた【文字列の初期化】の例。 #include <stdio.h> int main(void) { char str[] = "MARIO"; printf("%s\n",str); return 0; } ●【配列の初期化】の説明 型名 配列名[要素数]={0番の数値,1番の数値,2番の数値,・・・}; ●【配列を初期化】して表示する例 #include <stdio.h> #include <stdio.h> int main(void) { int array[10] = {42,79,13}; printf("array[0] = %d\n",array[0]); printf("array[1] = %d\n",array[1]); printf("array[2] = %d\n",array[2]); printf("array[3] = %d\n",array[3]); printf("array[4] = %d\n",array[4]); return 0; } ●for文の説明 for (初期化;条件式;更新) { 繰り返す文; } 初期化とは、カウント変数の初期化を行うための文です。 ここに書かれた式は、最初に1回だけ実行されます。 【初期化】とは、場面場面で意味が変わるのでしょうか。 どの場面に通じる【初期化】の本来の意味を教えてください。 よろしくお願い致します。