- ベストアンサー
構造体の配列について(2)
前回「構造体の配列について」という質問タイトルで、質問させていただいたのですが、理解が完全ではないため同じようなプログラム内容ではありますが、疑問を書かせていただきます。よろしくお願いします。 ----------------------------------------------------------------------- #include<stdio.h> #include<string.h> struct person{ char name[1]; //// (1) //// int height; int weight; }; int main() { struct person dt[10]; strcpy(dt[0].name,"日本一郎"); strcpy(dt[2].name,"関東次郎"); strcpy(dt[9].name,"関西三郎"); dt[1].weight=99; dt[1].height=168; printf("%s %s %s %d %d \n",dt[0].name,dt[2].name,dt[9].name,dt[1].weight,dt[1].height); return 0; } ----------------------------------------------------------------------- 以上のプログラムの(1)の部分で、文字を1文字しか格納出来ないのに、 strcpy(dt[0].name,"日本一郎"); strcpy(dt[2].name,"関東次郎"); strcpy(dt[9].name,"関西三郎"); としても何故正しく実行できるのかわかりません。 前回いろいろとご回答いただいたのに、しっかりと理解できない者ですが、教えていただければ嬉しいです。
- みんなの回答 (14)
- 専門家の回答
質問者が選んだベストアンサー
>#11のお礼 ◎2 ちょっと違います。この場合height weightともにまだデータが破壊されていません。 私の回答が少しわかりづらかったですね。補足しておきます。 私は2つの現象について言及しています。 例1 >不正に書き換えた後(メモリ破壊)に、そこになにかを書き込むまえに読み込むと、値が不正になっているのです。 ---------------------------------------------- dt[0].height = 168; //(1) dt[0].weight = 99; //(2) strcpy( dt[0].name, "日本一郎" ); //(3) printf( "氏名: %s\n", dt[0].name ); printf( "身長: %d\n", dt[0].height ); printf( "体重: %d\n", dt[0].weight ); ---------------------------------------------- この場合、(1),(2)で設定された値が(3)で不正に書き換えられています。そして、不正な値が表示されるでしょう。 例2 >そこ(破壊された場所)に値を書き込む操作自体には影響が出ません。 ---------------------------------------------- strcpy( dt[0].name, "日本一郎" ); //(1) dt[0].height = 168; //(2) dt[0].weight = 99; //(3) //printf( "氏名: %s\n", dt[0].name ); //(4) printf( "身長: %d\n", dt[0].height ); printf( "体重: %d\n", dt[0].weight ); ---------------------------------------------- この場合も、(1)が不正なデータの書き換えをしています。 ところが、その後(2)(3)でデータを書き換えます。 結果、表示時に身長と体重は正常なデータです。 なお、(4)で名前を表示しようとすると、データの内容が1文字目しか維持できる保証がないために、何が起きるかわかりません。なので、コメントアウトしてあります。 なお、#9のお礼では、あなたは例2の場合に(2)(3)の操作自体が不正だと考えているように読めましたので、#11での指摘となりました。
その他の回答 (13)
- zwi
- ベストアンサー率56% (730/1282)
今までのやり取りを見ていて、やはり実際のメモリイメージを見たほうが理解できると思いますので簡易メモリダンプを付けてみました。 #include <stdio.h> struct person{ char name[1]; int height; int weight; }; void memdump(void *pmem,int size);//プロトタイプ int main() { struct person dt[2]; memset(dt,0xff,sizeof(dt));//0xffで埋め尽くし memdump(dt,sizeof(dt)); dt[0].height = 168; memdump(dt,sizeof(dt)); dt[0].weight = 99; memdump(dt,sizeof(dt)); strcpy( dt[0].name, "abcdefghijklm" ); memdump(dt,sizeof(dt)); printf( "氏名: %s\n", dt[0].name ); printf( "身長: %d\n", dt[0].height ); printf( "体重: %d\n", dt[0].weight ); return 0; } // 簡易メモリダンプ void memdump(void *pmem,int size) { unsigned char *pbyte = (char*)pmem; int i; for( i=0 ; i<size ; i++ ) { if( (0x20 <= pbyte[i]) && (pbyte[i] <=0x7f) ) { printf( " %c", pbyte[i] ); } else { printf( " ." ); } if( (i%4)==3 ) printf( " " ); } printf( "\n" ); for( i=0 ; i<size ; i++ ) { printf( "%02X", pbyte[i] ); if( (i%4)==3 ) printf( " " ); } printf( "\n" ); } 日本語表示は面倒だったので英数字オンリーです。それとint系の数値はリトル・エンディアンですので値の並びが4バイトで逆に並んでいるので注意してください。 実行結果は、こんな感じです。 . . . . . . . . . . . . . . . . . . . . . . . . FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF . . . . . . . . . . . . . . . . . . . . . . . . FFFFFFFF A8000000 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF . . . . . . . . c . . . . . . . . . . . . . . . FFFFFFFF A8000000 63000000 FFFFFFFF FFFFFFFF FFFFFFFF a b c d e f g h i j k l m . . . . . . . . . . . 61626364 65666768 696A6B6C 6D00FFFF FFFFFFFF FFFFFFFF 氏名: abcdefghijklm 身長: 1751606885 体重: 1818978921 上のダンプで数値のA8000000が168で63000000が99になります。このダンプを活用して色々と試してみてください。
お礼
ご回答ありがとうございます。 まだ理解していない関数があるため、分からない部分はありますが、このプログラムにより、メモリイメージが可視化できるので、理解できれば、かなり理解が深まると思います!
- asuncion
- ベストアンサー率33% (2127/6290)
>◎2のようにメモリ破壊部に「.height」、「.weight」は設定できるが、値が不正になっているという事でしょうか? >dt[0].height = 168; >dt[0].weight = 99; ここまでは正しいです。ところが、 >strcpy( dt[0].name, "日本一郎" ); これで、nameのために確保してあった大きさ(1バイト)を超えて書込んでいます。 heightとweightの領域の内容を破壊しています。 その結果、 >printf( "身長: %d\n", dt[0].height ); >printf( "体重: %d\n", dt[0].weight ); ここで出力しているheightとweightの値が、意図とは異なる内容になってしまいます。
お礼
>dt[0].height = 168; >dt[0].weight = 99; 上記までは確保されているメモリにしっかりと設定されるが、 >strcpy( dt[0].name, "日本一郎" ); この1文で「height」と「weight」のメモリが破壊され意図と異なる内容になってしまうということですね!
- S117
- ベストアンサー率40% (18/45)
>#9へのお礼 いや、やっぱり理解できてないと思います。 メモリ破壊というのは、メモリのデータを不正に書き換えることを意味しています。(メモリのデータが破壊されるのを省略してメモリ破壊と称している。) なので、そこに値を書き込む操作自体には影響が出ません。 不正に書き換えた後(メモリ破壊)に、そこになにかを書き込むまえに読み込むと、値が不正になっているのです。 それぞれの詳しいことについては、各回答者がすでに書いているので省きます。 以下は、直接質問と関係ないですが、アドバイスです。 もう少し落ち着いて、じっくりと各回答を読みましょう。また、一般にプログラムの動作というものはとても厳密です。なのでそれについての解説の文章もとても厳密なので、変に省略したり言い換えたりしない方が、確実に理解できます。また、自分で説明するときも意味が正しいかよく考えてください。
お礼
ご回答ありがとうございます。 >そこに値を書き込む操作自体には影響が出ません。 >不正に書き換えた後(メモリ破壊)に、そこになにかを書き込むまえに読 >み込むと、値が不正になっているのです。 以上の内容は、 ◎1------------------------------------- struct person{ char name[1]; int height; int weight; }; int main() { struct person dt[10]; //dt[0].height = 168; //dt[0].weight = 99; strcpy( dt[0].name, "日本一郎" ); printf( "氏名: %s\n", dt[0].name ); return 0; } ------------------------------------- ◎2------------------------------------- struct person{ char name[1]; int height; int weight; }; int main() { struct person dt[10]; dt[0].height = 168; dt[0].weight = 99; strcpy( dt[0].name, "日本一郎" ); printf( "氏名: %s\n", dt[0].name ); printf( "身長: %d\n", dt[0].height ); printf( "体重: %d\n", dt[0].weight ); return 0; } ------------------------------------- ◎1に対して、◎2のようにメモリ破壊部に「.height」、「.weight」は設定できるが、値が不正になっているという事でしょうか?
こんにちは。 > dt[0].height = 168; > dt[0].weight = 99; > strcpy( dt[0].name, "日本一郎" ); //// (1) //// > > (1)を設定した時点で、確保したメモリ領域を超えてしまい、「.height」、「.weight」のメモリを破壊 > してしまう。という理解でよろしいでしょうか? そのとおりです。 もちろん、「.name」 が充分にエリア確保が行われているのであれば、問題ありません。 ですので、C言語(に限りませんが)でこういった処理を行う場合、プログラマーは、メモリイメージを 想像しながら、「この記述で問題がないか?」などを意識してコードを記述するクセをつけた方が良い かもしれません。
- Tacosan
- ベストアンサー率23% (3656/15482)
#5 に対して「dt[0].namneの内容がおかしくなるという感じですかね?」って書いてるけど, まさにその実例が #3 だってことは理解できてる?
お礼
strcpy(dt[1].name,"北風小僧"); 確保した領域を超えて以上の文字列を設定していて、 dt[1].weight=99; dt[1].height=168; を破壊されたメモリに設定しようとしているため、不具合が起きるでよいでしょうか? 皆さんのアドバイスのおかげで、メモリのイメージが出来るようになりました!
こんにちは。 >確保したメモリ領域を越えて、メモリを使ったため不具合が起こっているという考えなのでしょうか? 『考え』というよりも、実際にそういう現象(他のエリアを破壊する不具合)が発生してしまうということです。 ご質問のプログラムでは、たまたま、壊されたエリアに対して参照・表示等の処理を行っていませんので、 不具合が目に見える形として表面に現れていないだけです。 繰り返しになってしまいますが、少し置き換えた例を上げてみます。 注)下記の例では、char型のデータサイズ=1バイト、int型のデータサイズ=4バイト、 文字コード1文字(全角)のコードサイズ=2バイトとし、また、コンパイラの設定と して、構造体のアライメント指定(境界サイズ指定)を8バイトとした場合の例です。 ■ケース1(正常なケース) --------------------------------------------------- struct person{ char name[64]; //氏名 ←エリア確保が充分な場合 //(氏名の文字列長が64バイト未満と限定した場合) int height; //身長 int weight; //体重 }; struct person dt[10]; dt[0].height = 168; dt[0].weight = 99; //@1 strcpy( dt[0].name, "日本一郎" ); //@2 printf( "氏名: %s\n", dt[0].name ); printf( "身長: %d\n", dt[0].height ); printf( "体重: %d\n", dt[0].weight ); //@3 --------------------------------------------------- 1)@1の実行後のdt[0]のメモリイメージ ┌─── ~~ ────┬───┬───┐ │ 不定 │ 168 │ 99 │ └─── ~~ ────┴───┴───┘ .name .height .weight 2)@2の実行後のdt[0]のメモリイメージ ┌─── ~~ ────┬───┬───┐ │ "日本一郎"+'\0' │ 168 │ 99 │ └─── ~~ ────┴───┴───┘ .name .height .weight ※.nameエリア内において、格納された文字列の末尾コード('\0')以降の余った 領域は不定データのままです。 3)@3の実行後の表示結果 氏名: 日本一郎 身長: 168 体重: 99 ■ケース2(不具合が発生するケース) --------------------------------------------------- struct person{ char name[1]; //氏名 ←エリア確保が充分でない場合 int height; //身長 int weight; //体重 }; struct person dt[10]; dt[0].height = 168; dt[0].weight = 99; //@1 strcpy( dt[0].name, "日本一郎" ); //@2 printf( "氏名: %s\n", dt[0].name ); printf( "身長: %d\n", dt[0].height ); printf( "体重: %d\n", dt[0].weight ); //@3 --------------------------------------------------- 1)@1の実行後のdt[0]のメモリイメージ ┌───┬───┬───┐ │不定 │ 168 │ 99 │ └───┴───┴───┘ .name .height .weight ※.nameの宣言時の配列個数は1個ですが、実際はアライメントにより4バイト分 確保されます。(※ただしコンパイラなどの環境設定に依存します。) 2)@2の実行後のdt[0]のメモリイメージ ┌───┬───┬───┐ │"日本" "一郎" '\0' │ └───┴───┴───┘ .name .height .weight ※ここで、.nameエリアの先頭アドレスから、文字列 "日本一郎" がコピーされて しまうので、.nameエリアの境界を越えて .height以降のエリアにも文字列がコピー されてしまいます。(文字列データは末尾コード'\0'も含まれます) 従って、それまでセットされていたデータは上書きされて(壊されて)しまいます。 3)@3の実行後の表示結果 氏名: 日本一郎 ←ここは、正常に氏名が表示されてしまいます。 printfの書式で %s と指定されていて、それに対応する引数が 文字列 エリアの先頭ポインタ(dt[0].name)となっていますので、printf関数は その先頭ポインタから末尾コード'\0'が現れるまでのデータを文字列と みなして表示出力します。 身長: xxxxxx ←ここは本来、数値の168(0x000000A8)が表示されるのを期待すべき ところですが、上書きされた(壊された)ため、想定外のデータが表示 されてしまいます。 (実際は、文字列”一郎"の文字コードが数値として表示されてしまう) 体重: 0 ←ここは本来、数値の99(0x00000063)が表示されるのを期待すべき ところですが、上書きされた(壊された)ため、想定外のデータが表示 されてしまいます。 (実際は、エリアの最初の1バイトが文字列の末尾コード'\0'で上書き され、結果として数値の0が表示されてしまいます)
お礼
ご回答ありがとうございます。 ご回答にあるメモリイメージとてもわかりやすいです。 char name[1]; int height; int weight; 以上の変数名を宣言して、必要なメモリが確保されるのですが、 dt[0].height = 168; dt[0].weight = 99; strcpy( dt[0].name, "日本一郎" ); //// (1) //// (1)を設定した時点で、確保したメモリ領域を超えてしまい、「.height」、「.weight」のメモリを破壊してしまう。 という理解でよろしいでしょうか?
- asuncion
- ベストアンサー率33% (2127/6290)
構造体の配列に限らず、一般に配列において、 定義した範囲を超えて代入してはいけません。 それは、プログラムを書く側が注意しなければなりません。 C言語の処理系は、ほとんど何もしてくれません。せいぜい、実行時にプログラムを異常終了させてくれるくらいです。
お礼
>構造体の配列に限らず、一般に配列において、 >定義した範囲を超えて代入してはいけません。 以上のご回答しっかり頭に入れて起きます!
- asuncion
- ベストアンサー率33% (2127/6290)
>(1)の順番で、メンバ名を宣言したら、(2)も(1)と同じ順番で書かなければいけないんですかね?? そういうことはありません。 構造体の定義順と、メンバーへのアクセス順には 何も関係がありません。
お礼
ご回答ありがとうございます。 メンバーへのアクセス順には何も関係がないという事理解できました! ---------------------------------------------- struct person{ char name[80]; int height; int weight; }; int main() { struct person dt[10]={"日本一郎",180,70}; printf("%d %d %s\n",dt[0].weight,dt[0].height,dt[0].name); --------------------------------------------------- 以上のように{ }を使って、初期化する場合はメンバを宣言した順に、{ }の中身も宣言しなければいけないですよね?
- fatbowler
- ベストアンサー率48% (26/54)
初心者にやさしく書きますね。 strcpy(dt[0].name,"日本一郎"); が一見正しく動いているように見えるのは、このコードが 「指定されたアドレスから始まる領域に、"日本一郎"という 文字列をコピーする」 という意味だからです。 領域が確保されているかどうかはお構いなしにメモリに値を セットしてしまう、ということを平気でやってしまうのがC言語の 特徴でもあります。 確保した領域を超えて値をセットしてしまうことを「メモリ破壊」 と呼び、メモリリーク(メモリ解放漏れ)と共に見つけにくい バグの代表格です。 例にdt[0].weightやdt[0].heightではなく、dt[1].weighやtdt[1].height を使ったのは、dt[0].weightやdt[0].heightに値を入れると、 dt[0].nameの内容がおかしくなったからではありませんか? 問題が起こっているのに、敢えて見えないふりをしているだけで、 正しく実行できているとは言えないのです。
お礼
ご回答ありがとうございます。 >領域が確保されているかどうかはお構いなしにメモリに値を >セットしてしまう、ということを平気でやってしまうのがC言語の >特徴でもあります。 >確保した領域を超えて値をセットしてしまうことを「メモリ破壊」 >と呼び、メモリリーク(メモリ解放漏れ)と共に見つけにくい >バグの代表格です。 上記の内容、今勉強中ですが、大まかにはイメージできるようになりました! strcpy(dt[0].name,"日本一郎");に対して、dt[1].weight=99; dt[1].height=168;というようにdt「1」に設定したのは、たまたま ですが(;^_^)、dt[0]としてしまうと破壊されたメモリにアクセスしてしまい、dt[0].namneの内容がおかしくなるという感じですかね?
補足
struct person{ char name[1]; //// int height; //// (1) int weight; //// }; int main() { struct person dt[10]; strcpy(dt[1].name,"日本一郎"); //// dt[1].height=168; //// (2) dt[1].weight=99; //// (1)の順番で、メンバ名を宣言したら、(2)も(1)と同じ順番で書かなければいけないんですかね??
- chie65536(@chie65535)
- ベストアンサー率44% (8800/19959)
>何故正しく実行できるのかわかりません。 「確保した領域を越えて他人のメモリを使ったとしても、他人がそのメモリを使わない限り、自分に不都合は起きない」ので「正しく実行」してしまうのです。 intが4バイトと仮定して、メモリの中身を見ると +000 dt[0].name[0] +001 空き +002 空き +003 空き +004 | dt[0].height +007 +008 | dt[0].weight +011 +012 dt[1].name[0] +013 空き +014 空き +015 空き +016 | dt[1].height +019 +020 | dt[1].weight +023 +024 dt[2].name[0] +025 空き +026 空き +027 空き +028 | dt[2].height +031 +032 | dt[2].weight +035 略 +108 dt[9].name[0] +109 空き +110 空き +111 空き +112 | dt[9].height +115 +116 | dt[9].weight +119 となっています。 ここで strcpy(dt[0].name,"日本一郎"); を実行すると +000 '日'の1バイト目 dt[0].name[0] +001 '日'の2バイト目 空き +002 '本'の1バイト目 空き +003 '本'の2バイト目 空き +004 | '一郎'の4バイト dt[0].height +007 +008 | 1バイト目に'\0' dt[0].weight +011 +012 不変 dt[1].name[0] +013 不変 空き +014 不変 空き +015 不変 空き +016 | 不変 dt[1].height +019 +020 | 不変 dt[1].weight +023 +024 不変 dt[2].name[0] +025 不変 空き +026 不変 空き +027 不変 空き +028 | 不変 dt[2].height +031 +032 | 不変 dt[2].weight +035 略 +108 不変 dt[9].name[0] +109 不変 空き +110 不変 空き +111 不変 空き +112 | 不変 dt[9].height +115 +116 | 不変 dt[9].weight +119 となります。次に strcpy(dt[2].name,"関東次郎"); を実行すると +000 '日'の1バイト目 dt[0].name[0] +001 '日'の2バイト目 空き +002 '本'の1バイト目 空き +003 '本'の2バイト目 空き +004 | '一郎'の4バイト dt[0].height +007 +008 | 1バイト目に'\0' dt[0].weight +011 +012 不変 dt[1].name[0] +013 不変 空き +014 不変 空き +015 不変 空き +016 | 不変 dt[1].height +019 +020 | 不変 dt[1].weight +023 +024 '関'の1バイト目 dt[2].name[0] +025 '関'の2バイト目 空き +026 '東'の1バイト目 空き +027 '東'の2バイト目 空き +028 | '次郎'の4バイト dt[2].height +031 +032 | 1バイト目に'\0' dt[2].weight +035 略 +108 不変 dt[9].name[0] +109 不変 空き +110 不変 空き +111 不変 空き +112 | 不変 dt[9].height +115 +116 | 不変 dt[9].weight +119 となります。次に strcpy(dt[9].name,"関西三郎"); を実行すると +000 '日'の1バイト目 dt[0].name[0] +001 '日'の2バイト目 空き +002 '本'の1バイト目 空き +003 '本'の2バイト目 空き +004 | '一郎'の4バイト dt[0].height +007 +008 | 1バイト目に'\0' dt[0].weight +011 +012 不変 dt[1].name[0] +013 不変 空き +014 不変 空き +015 不変 空き +016 | 不変 dt[1].height +019 +020 | 不変 dt[1].weight +023 +024 '関'の1バイト目 dt[2].name[0] +025 '関'の2バイト目 空き +026 '東'の1バイト目 空き +027 '東'の2バイト目 空き +028 | '次郎'の4バイト dt[2].height +031 +032 | 1バイト目に'\0' dt[2].weight +035 略 +108 '関'の1バイト目 dt[9].name[0] +109 '関'の2バイト目 空き +110 '西'の1バイト目 空き +111 '西'の2バイト目 空き +112 | '三郎'の4バイト dt[9].height +115 +116 | 1バイト目に'\0' dt[9].weight +119 となります。次に dt[1].weight=99; dt[1].height=168; を実行すると +000 '日'の1バイト目 dt[0].name[0] +001 '日'の2バイト目 空き +002 '本'の1バイト目 空き +003 '本'の2バイト目 空き +004 | '一郎'の4バイト dt[0].height +007 +008 | 1バイト目に'\0' dt[0].weight +011 +012 不変 dt[1].name[0] +013 不変 空き +014 不変 空き +015 不変 空き +016 | 168 dt[1].height +019 +020 | 99 dt[1].weight +023 +024 '関'の1バイト目 dt[2].name[0] +025 '関'の2バイト目 空き +026 '東'の1バイト目 空き +027 '東'の2バイト目 空き +028 | '次郎'の4バイト dt[2].height +031 +032 | 1バイト目に'\0' dt[2].weight +035 略 +108 '関'の1バイト目 dt[9].name[0] +109 '関'の2バイト目 空き +110 '西'の1バイト目 空き +111 '西'の2バイト目 空き +112 | '三郎'の4バイト dt[9].height +115 +116 | 1バイト目に'\0' dt[9].weight +119 のようになります。 メモリの中では、dt[0].height、dt[0].weight、dt[2].height、dt[2].weight、dt[9].height、dt[9].weightが文字列に潰されて「大変な事になって」います。 でも「だれも、それに代入もしてないし、参照も表示もしてない」ので「大変な事になっていても、何の影響も無い」のです。 なので「他人のメモリをどんだけ壊そうが、何の影響も無いなら、正しく動いてしまう」のです。 文字列が「全角文字で4文字」だったから、壊れたのが8バイトで済み、大きな影響が無かっただけです。(1バイトしか無い領域に、8バイト+終端1バイトの9バイトを書き込んだので、壊れたバイト数は、9-1=8で、8バイトです) 名前を長くして、例えば strcpy(dt[0].name,"日本一郎"); を strcpy(dt[0].name,"じゅげむじゅげむごこうのすりきれかいじゃりすいぎょのすいぎょうまつうんらいまつふうらいまつくうねるところにすむところやぶらこうじのやぶこうじぱいぽぱいぽぱいぽのしゅーりんがんしゅーりんがんのぐーりんだいぐーりんだいのぽんぽこぴーのぽんぽこなーのちょうきゅうめいのちょうすけ"); に変えてみましょう。 多分、構造体配列の領域を越えて、メモリ中の色々な重要データも破壊するので、プログラムが例外を吐いて落ちるか、不正なアクセスをしたと言う警告が出て止まります。
お礼
いつもご丁寧に詳しくご回答ありがとうありがとうございます。 >「確保した領域を越えて他人のメモリを使ったとしても、他人がそのメモリ >を使わない限り、自分に不都合は起きない」ので「正しく実行」してしまう >のです。 上記の内容とメモリのイメージ頑張って理解してみます!
- 1
- 2
お礼
>strcpy( dt[0].name, "日本一郎" ); //(1) >dt[0].height = 168; //(2) >dt[0].weight = 99; //(3) >//printf( "氏名: %s\n", dt[0].name ); >printf( "身長: %d\n", dt[0].height ); >printf( "体重: %d\n", dt[0].weight ); (1)により「height」、「weight」のメモリに不正なデータ書き換えを行っても、(2)、(3)を後に書くことにより、「height」、「weight」のメモリに正常なデータを書き換えられるということですね!