質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

structure

このタグは、プログラム言語におけるデータ型structure(構造体)に関するタグです。

Q&A

解決済

5回答

4752閲覧

C言語の構造体を、メンバ変数の指定なしでPrintfする際の、挙動について

_fox

総合スコア15

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

structure

このタグは、プログラム言語におけるデータ型structure(構造体)に関するタグです。

0グッド

1クリップ

投稿2022/03/09 15:34

編集2022/03/09 17:08

こんにちは。C言語の構造体について質問があります。
下記のようなコードを書いて構造体の動作を確認しています。
疑問点は、コードの下から3行目と4行目の内容です。

構造体の実体をPrintfしたら、構造体の先頭にあるメンバ変数の値だけが出力されると思っていたら、
メンバ変数が全て出力できました。

この動きがよくわからないのですが、
構造体の中身を出力しようとしたときに、
具体的なメンバ変数を指定しない場合は、全てのメンバ変数の値が出力される仕様になっているという事なのでしょうか?

もしどなたか詳しい方がいらっしゃいましたら、教えていただけますと幸いです。

C

1#include <stdio.h> 2 3struct animal 4{ 5 /* data */ 6 int eye; 7 double weight; 8}; 9 10int main(void){ 11 struct animal animal = {0}; 12 struct animal *panimal = &animal; 13 panimal->eye=11; 14 15 //構造体animalの実体のメンバ変数eyeにアクセスしています 16 //全部11が出力されます 17 printf("%d\n",panimal->eye); 18 printf("%d\n",animal.eye); 19 printf("%d\n",(*panimal).eye); 20 21 //構造体animalの実体のメンバ変数eyeのアドレスを表示しています 22  //全部同じアドレスが出力されます 23 printf("%p\n",panimal); 24 printf("%p\n",&(panimal->eye)); 25 printf("%p\n",&animal); 26 printf("%p\n",&animal.eye); 27 28 //↑ここまでは想定していた動きでした。 29  30 //↓これら2つが何が起きているのか分かりません…… 31 printf("%d,%f\n",*panimal); //←*panimalはアドレス&animalが指す値なので、メンバ変数eyesの値だけが出力されるはずではないですか? 32 //しかし実際は、-Wformatオプションでビルドするとワーニングが出るものの、なぜかメンバ変数が2つとも読めました。理由がわかりません。 33 printf("%d,%f\n",animal); //←もしかしてこれも行けるかもと思って書いてみました、これもメンバ変数が2つとも読めて、何が起きているのかわかりません。 34 35 return 0; 36}

追記します。
構造体を配列として宣言して使った場合、メンバ変数を指定せずにPrintfすることはできませんでした。

C

1#include <stdio.h> 2 3//構造体の型宣言 4struct student{ 5 int no; // 学籍番号 6 char name[256]; // 氏名 7 int year; // 学年 8 char s_class[256]; // クラス 9}; 10 11int main(void){ 12 int i; 13 //構造体の宣言と初期化の代入 14 struct student students[4] = { 15 {1, "nakano", 1, "c"}, 16 {2, "ooisi", 2, "b"}, 17 {3, "okamoto", 1, "a"}, 18 {4, "minabe", 2, "a"}, 19 }; 20 21 for(i = 0; i < 4; i++) { 22 //結果の出力 23      //コメントアウトしているコレは意図通りに要素を表示してくれます↓ 24 //printf("%d,%s,%d,%s\n", students[i].no, students[i].name, students[i].year, students[i].s_class); 25       26     //↓これは同じようにワーニングが出るのですが、実行してもデータは取れませんでした。 27    printf("%d,%s,%d,%s\n",students[i]); 28 } 29 30 return 0;

更に追記します。
jimbeさんにコメントいただいて、

仮に配列みたいにスタックしていて、本来はエラー吐きそうな実装だけれども、たまたまうまくいっているだけと考えると、
ポインタをインクリメントしたり、糖衣構文じゃない配列の値出力の書き方でデータが読み出せるんじゃないかと思って、

C

1//ポインタをインクリメントするパターン 2printf("--%d\n",*(&animal+0)); 3printf("--%f\n",*(&animal+1)); 4//糖衣構文じゃない配列の書き方 5printf("%d\n",0[&animal]); 6printf("%f\n",1[&animal]);

こんな文を追記してみたところ、私の環境では、両方とも11と0.0が出力されました!(びっくり!)
確かにこれが動いたということは、スタックフレームで実現されているのかもしれませんね。

更に配列と同じ書き方もしてみましたが、さすがにこちらはエラーが出力されました。

C

1 //これは動かない 2 printf("%d\n",&animal[0]); 3 printf("%f\n",&animal[1]);

初心者なものでちょっと不安なのですが、
基本的には、「C言語の構造体を、メンバ変数の指定なしでPrintfしたりしない。Printfするときは必ずメンバ変数を指定する」という風に覚えておけばいいんですかね?

それとも、日常的にprintf("%d,%f\n",animal);的な、メンバ変数を指定しない出力する文を書いたりされますか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

printf関数さんにとっては、フォーマット文字列に従って、その順番のその型の変数を引数エリア(スタック)より読み込んで、その値を表示しています。
printfを実行するほうは、フォーマット文字列と、構造体(のデータパターン)を順にスタックに押し込んで、printfの関数を実行させています。

たまたま、構造体のデータパターンが、フォーマット文字列での変数型並びに一致していた、ということなんでしょう。

あくまで、たまたまこういう結果になった、という程度のことですね

具体的なメンバ変数を指定しない場合は、全てのメンバ変数の値が出力される仕様になっているという事なのでしょうか?

残念ながら、そういう仕様は存在しません

投稿2022/03/09 17:04

編集2022/03/09 17:06
y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

仕様は分かりませんが、想像は出来ます。
パラメータがスタックフレームで実現されているとすると、構造体の 2 件が順に入れられ、 printf はフォーマットに従って 2 件のデータを順に表示したのではないでしょうか。

投稿2022/03/09 16:30

jimbe

総合スコア12648

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

言語仕様 (C99) の 7.19.6.1 にこうあります。

If there are insufficient arguments for the format, the behavior is undefined.
(書式に対して実引数が不足しているときの動作は,未定義とする。)

また、「未定義」については 3.4.3 にこうあります。

Possible undefined behavior ranges from ignoring the situation completely with unpredictable results
(未定義の動作に対して,その状況を無視して予測不可能な結果を返してもよい。)

つまり、質問者が提示した状況は、言語仕様の規則においては全くデタラメの動作をしてもかまわないということです。 事実上禁止されていると考えてもよいです。

投稿2022/03/11 09:03

SaitoAtsushi

総合スコア5444

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

関数に構造体(構造体変数)を引数で渡すことはできるので、printf() に構造体を渡すこともできなくないでしょう。
でも、構造体を渡せば全てのメンバ変数を出力する、という仕様は printf() にありません。

私の説明に必要な部分を簡略化したコードで示します。

C

1#include <stdio.h> 2struct animal { 3 int eye; 4 double weight; 5}; 6int main(void) { 7 struct animal animal = { 11, 3.1415 }; 8 printf("%d\n", animal.eye); 9 printf("%f\n", animal.weight); 10 printf("%d, %f\n", animal); 11 printf("sizeof animal = %ld\n", sizeof animal); 12 return 0; 13}

これを私の手元の32bit版GCCで動作させた結果は

bash

1# ./a.exe 211 33.141500 411, -610.304200 5sizeof animal = 16

論より証拠、全く異なる値が表示されました。
同時に64bit版のGCCでも動作させました。結果はこうです。

bash

1$ ./a.out 211 33.141500 411, 3.141500 5sizeof animal = 16

と、正しく(?)表示されました。なので質問者のコンパイラも64bit版だと思います。

構造体 animal のサイズも表示させました。どちらも16です。
普通 sizeof int は4,sizeof doubleは8で合計12と思いきや、16です。
メンバ変数 eye と weight の間に4バイトの隙間があるのです。

さて、関数に構造体を渡そうとすれば、引数の領域に構造体分(この場合は16バイト)のメモリが割当てられ、そこに animal の値を代入し、それが関数=この場合は printf()に渡ります。

しかし printf() は animal 構造体の定義を知りませんから、隙間があることも知りません。"%d, %f\n" という文字列(変換指定)に従って、引数領域を表示するだけです。

その結果、32bit版では隙間部分を %f で表示することになってしまい全く違う値が表示されたのです。こういう事を、巷ではコンパイラの未定義動作とか実装依存とか呼んでるようです。ある環境でうまくいったとしても他所でうまくいく保証の無い事なので、やっちゃいけないことなんです。

では64bitコンパイラでうまくいったのはなぜか。
32bitコンパイラと64bitコンパイラでは引数領域の構造に違いがあるのです。それがたまたま上手くいった理由と関係しています。その違いを具体的に説明するのは、面倒なのでパスしたい。

ご注意。隙間ができるかどうかもコンパイラ次第、隙間ができない場合もあります。

投稿2022/03/11 00:31

編集2022/03/11 13:36
rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

みなさま、初心者の疑問に親切にお答えいただきありがとうございました!
無事納得することができ、更にC言語やコンパイラへの理解を深めることができました。

お忙しいところ、ご回答いただき本当にありがとうございます!
本当はすべての回答にベストアンサーをつけさせていただきたかったのですが、実例を交えて解説いただいたrubato6809さんにベストアンサーを設定させていただきたく思います。私の環境でも試してみたところ、本当にその動きになり理解の助けになりました。ありがとうございます

投稿2022/03/12 03:44

編集2022/03/12 03:47
_fox

総合スコア15

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2022/03/12 05:08

BA、ありがとうございます。 それは32bit版でも試すことができたということですか?もしそうなら私のコードの一行を printf("%d, %d, %f\n", animal); と変えてみると面白いことがわかりますよ。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問