構造体のポインタの動作及びそれ故書けない関数

このQ&Aのポイント
  • C言語の構造体ポインタの動作について質問しています。
  • ポインタの動作により自分のやりたい書き方ができないため、アドバイスを求めています。
  • ポインタの動作とその影響についての説明を希望しています。
回答を見る
  • ベストアンサー

構造体のポインタの動作及びそれ故書けない関数

こんにちは.c言語でのstructureのポインタについての質問,及びその動作ゆえに自分のやりたい書き方ができないので,アドバイスをお願いしたいと思います. まずは,structureのポインタを使った計算が謎の動きをするので,説明して頂けたらと思います.ubuntu 12.04 64bit, コンパイラはgcc 4.6.3, ただg++でやっても同じ動きでした. #include <stdio.h> int main(int argc, char **argv) { struct state_ { double pos[3]; double att[3]; }state = { { 1.0, 2.0, 3.0 }, { 4.0, 5.0, 6.0 }, }; double *state_ptr; state_ptr = &state.pos[0]; printf("%f\t%f\t%f\t%f\t%f\t%f\n", *state_ptr, *(++state_ptr),*(++state_ptr), *(++state_ptr), *(++state_ptr), *(++state_ptr)); double *pos_ptr, *att_ptr; pos_ptr = &state.pos[0]; att_str = &state.att[0]; printf(“head of pos %f, head att %f\n”, *pos_ptr, *att_ptr); return 0; } こうすると,結果は 6.000000 6.000000 6.000000 6.000000 6.000000 6.000000 head of pos 1.000000, head of att 4.000000 はて,どうなっているのでしょうか?少なくとも1行目の最初の6.0はpos[0]の1.0のはずなのでは? ちなみに-Wallをつけると, struct_double_pointer_test.c:17:54: warning: operation on ‘state_ptr’ may be undefined [-Wsequence-point] が出ます. また,++state_ptrをstate_ptr++に変えると結果は 6.000000 6.000000 5.000000 4.000000 3.000000 2.000000 そして -O2をつけると 6.000000 5.000000 4.000000 3.000000 2.000000 1.000000 基本的にメンバーの順番がひっくり返ってますね.これは一体どういうことなんでしょう?undefinedと言われてるから何が起こっても変じゃないでしょ,と言われればそうなんですが,何かなっとくのいく説明があれば嬉しいです.pos_ptr, att_ptrで単体で出すと正しく出るので,*(pointer++)の動作がundefinedってことなんですよね?僕は 1.0 2.0 3.0 4.0 5.0 6.0 となってくれると思った訳です.(ちなみにMacOS gccではこうなりました)structureの中で順番に上からアドレスが振られると思ったので.そして2番目の質問に移る訳ですが,上記の僕の予想通り動いた場合,2つ構造体のポインタを渡して,順番に中身を積分させていくような計算が以下のようにできると思いました. void integrate(struct state_ *x, struct state_ *dx, num_of_double) { const float timestep = 0.01 int i; for(i=0; i<num_of_double, i++){ *x += *dx*timestep; x++, dx++; } } 両方きれいに順番がひっくり返るなら上の書き方でも積分させた値は同じになるからいい?とは言えど怖くてもちろん使えません.こういう計算をさせる場合は,構造体のポイタを渡さずに配列ごとに計算させるんですかね?&pos[0]と&pos_dx[0]を渡すなど?一般的にどういう書き方をするものなのかが知りたいです.

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

  • ベストアンサー
  • mitoneko
  • ベストアンサー率58% (469/798)
回答No.4

 多分、犯人は、ここです。  >printf("%f\t%f\t%f\t%f\t%f\t%f\n", *state_ptr, *(++state_ptr),*(++state_ptr), *(++state_ptr), *(++state_ptr), *(++state_ptr));  このprintfの呼び出しですが、極めてまずいです。  関数を呼び出す時、関数に渡す引数の評価順序は、規定されていません。つまり、どんな順序で評価されるか解らないと言うことです。  このprintfでは、最初の*state_ptrの値を評価した後、次の引数に移る・・・という動作をしないと正しい動作になりません。  しかし、言語の定義では、そのような保証はどこにもありません。  残念ながら、どのような順番になるかは解りませんし、コンパイルするたびに違う順番になっても何も文句のつけようが無いのです。(コンパイラによる最適化が絡めば、充分に考えられる話です。)  ここでは、  double s0, s1, s2 以下略 ;  s0 = *state_ptr;  s1=*(++state_ptr);  中略  printf("%f\t%f\t%f\t%f\t%f\t%f\n",s0, s1, s2 以下略);  としてやれば、正しい順序が保証できるので、多分、あなたが思った結果になるでしょう。  関数の引数が、引数そのものに副作用を及ぼす場合には、このようなパターンには充分に注意する必要があります。

nasanaut
質問者

お礼

返信ありがとうございます. たしかに++が犯人でした.結果を表示させるだけのprintfが逆に問題になってしまうとは.

その他の回答 (4)

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

最後の integrate の例はなにをしたいんでしょうか?

nasanaut
質問者

お礼

返信ありがとうございます. この関数は例えばFOFE(First Order Forward Euler)を使って,ロボットや車の位置,速度,姿勢,角速度などを持った構造体の積分をするさいにまとめてステートを更新できます.

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

#1 ですが、書き忘れていたことがありました。 前の回答で「~に見える」と書いた計算の順番ですが、それはあくまでもそう見えるだけです。 動作が未定義である以上、特定の環境以下での実験結果をもとに処理順を仮定してそれに依存したコードを書いてはいけません。 その結果どのような問題が出るかは最適化オプションを付けるかつけないかで結果が変わるということで痛感しているはずです。

nasanaut
質問者

お礼

返信ありがとうございます. まさにその通りですね.最適化オプションで結果が変わってしまうコードは実は始めて見たので,いい勉強になりました.

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.2

> *state_ptr, *(++state_ptr),*(++state_ptr), これでが、順番通り (元の)state_ptr,state_ptr+1,state_ptr+2, ... になっている保証はありません。 過去の質問が参考になるでしょう。 http://okwave.jp/qa/q4894176.html

nasanaut
質問者

お礼

返信ありがとうございます.++が犯人でした.

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

undefined なのは同一の式の中で同一の変数に対して複数回インクリメントされているときにその処理順番がどうなっているかです。 http://social.msdn.microsoft.com/Forums/ja-JP/23f65ca3-9cf6-49b4-9975-23a26fee4f20/cc?forum=csharpexpressja 見たところ、 printf("%f\t%f\t%f\t%f\t%f\t%f\n", *state_ptr, *(++state_ptr),*(++state_ptr), *(++state_ptr), *(++state_ptr), *(++state_ptr)); と書いたとき、引数の値の決定はすべてのインクリメントが終わってからになっているようです。 一方、 printf("%f\t%f\t%f\t%f\t%f\t%f\n", *state_ptr, *(state_ptr++),*(state_ptr++), *(state_ptr++), *(state_ptr++), *(state_ptr++)); とするとインクリメントは後方から行われて、並行して前の演算が行われている影響が出ているように見え、-O2 を付けた状態では最適化によって state_ptr の値をコンパイルの時点で計算した結果左記の「前の演算が行われている影響」がないように見えます。 一方、integrate() には一つの式の中で同一の変数に対する複数回のインクリメントが行われていないため特に問題がないように見えます。

nasanaut
質問者

お礼

返信ありがとうございます.++が犯人でした.確かにintegrateの動作は予期したものになっていて,結果一番作りたいintegrateのチェックのために書いたprintfが問題になっていたという間抜けな展開でしたが,勉強にはなりました.

関連するQ&A

  • 構造体へのポインタの動的確保について

    構造体へのポインタを動的確保しようとmalloc関数を使用すると segmentation faultが起きます。 typedef struct cell{ char *word; int count; struct cell *next; }node_t; という構造体で node_t *ptr=(node_t*)malloc(sizeof(node_t)*num); という風に動的確保しようとするとsegmentation faultが起きました。 gdbを使って調べると Starting program: /home/programII/week05/a.out file1 file2 Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7b4ce36 in ?? () from /lib/x86_64-linux-gnu/libc.so.6 というメッセージが返ってきます。 これはライブラリとのリンクが正しく行われていないということでしょうか? しかし、ポインタの動的確保以外でmalloc関数を使用すると 正常に動作するのでライブラリ自体が無いわけではないようです。 ptr[2]といった風にポインタを参照したいのですが上手くいきません。 よろしくお願いします。

  • 構造体をポインターに入れたい

    構造体をポインターに入れたいのですがうまくいきません どうしたら入れることができますか? ―――ソース――― struct str{  char *name; }; static struct str state_ab[60][4] = {   { { { "いちご" } } , { { "みかん" } } ,{ { "レモン" } } , { { "ブドウ" } } },   { { { "" } } , { { "" } } ,{ { "" } } , { { "" } } }, }; static struct str state_ab2[60][8] = {   { { { "きゅうり" } } , { { "セロリ" } } ,{ { "じゃがいも" } } , { { "にんじん" } } , { { "たまねぎ" } } , { { "ニンニク" } } ,{ { "レタス" } } , { { "キャベツ" } } },   { { { "" } } , { { "" } } ,{ { "" } } , { { "" } } , { { "" } } , { { "" } } ,{ { "" } } , { { "" } } }, }; static struct str state_ab3[60][4] = {   { { { "牛肉" } } , { { "豚肉" } } ,{ { "鶏肉" } } , { { "魚肉" } } },   { { { "" } } , { { "" } } ,{ { "" } } , { { "" } } }, }; char *state_ab_f_read( int mode , int num1 , int num2 ){   struct str *p;   switch( mode ){     case 0: p = state_ab; break;     case 1: p = state_ab2; break;     case 2: p = state_ab3; break;   }   return p[ num1 ][ num2 ].name; } ―――エラー内容――― c:\documents and settings\user\my documents\visual studio 2008\projects\kami\state_ab_fd.cpp(237) : error C2440: '=' : 'str [60][4]' から 'str *' に変換できません。 指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。 c:\documents and settings\user\my documents\visual studio 2008\projects\kami\state_ab_fd.cpp(238) : error C2440: '=' : 'str [60][8]' から 'str *' に変換できません。 指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。 c:\documents and settings\user\my documents\visual studio 2008\projects\kami\state_ab_fd.cpp(239) : error C2440: '=' : 'str [60][4]' から 'str *' に変換できません。 指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。 c:\documents and settings\user\my documents\visual studio 2008\projects\kami\state_ab_fd.cpp(241) : error C2676: 二項演算子 '[' : 'str' は、この演算子または定義済の演算子に適切な型への変換の定義を行いません。(新しい動作; ヘルプを参照) c:\documents and settings\user\my documents\visual studio 2008\projects\kami\state_ab_fd.cpp(241) : error C2228: '.name' の左側はクラス、構造体、共用体でなければなりません ビルドログは "file://c:\Documents and Settings\USER\My Documents\Visual Studio 2008\Projects\kami\Debug\BuildLog.htm" に保存されました。

  • ポインタ型配列のポインタを構造体のポインタ変数に格納する方法教えて!_ver2

    int型の配列は構造体のポインタ型のint型変数にはキャストすればうまくコンパイルが通りますが、同じ方法でfloat型の配列は構造体のポインタ型のfloat型変数にはキャストしてポインタの値を入れてもうまくコンパイルできず困っています。ちなみにコンパイルエラーは「互換でない型変換」と表示されます。 返答のほど、よろしくお願いいたします。 #include<stdio.h> #include<malloc.h> float time[] ={2.2, 2.3, 2.4}; int time2[] ={2, 2, 2}; struct timelist{ float *time; int *time2; struct timelist *next; }*head; void main(void) { struct timelist *p; p = (struct timelist *)malloc(sizeof(struct timelist)); p -> time = (float *)time[0]; p -> time2 = (int *)time2[0]; printf("time = %f\n", p -> time); printf("time2 = %d\n", p -> time2); }

  • 構造体のメンバ参照について(C言語)

    初めて書き込みする,taroimotenです. 以下のプログラムを作ってみたのですが, メモリ参照エラーが表示され,うまく動きません. アドバイスおねがいします! #include <stdio.h> #include <stdlib.h> // 型宣言. typedef struct data DATA; typedef struct coords COORDS; // 構造体の定義. struct data{ COORDS *pnt[3]; }; struct coords{ float r1; float r2; }; int main(void) { DATA *ptr; DATA x; // ポインタ変数の初期化. ptr = &x; // メンバへの値の代入. ptr->pnt[0]->r1 = 100.0; printf(" r1 : %f \n",ptr->pnt[0]->r1); return EXIT_SUCCESS; }

  • 構造体ポインタ、及び、戻り値のあるマクロ関数の作成方法について

    ●開発環境 [OS] Linux 2.6.9 [コンパイラ] GCC 3.4.6 ●質問 以下のプログラム中のTEST関数を、 マクロ関数(#define)にしたいのですが、 やり方がわかりません。 ご存知の方がいらっしゃいましたら、ご教授願います。 -------------------------------- #include <stdio.h> typedef struct t_str_abc { unsigned int x; unsigned int y; } str_abc; unsigned int TEST( str_abc *kou ); void main(void) { str_abc kouzou; unsigned int ret; kouzou.x = 1; kouzou.y = 2; ret = TEST(&kouzou); printf("ret = %d\n", ret); } unsigned int TEST( str_abc *kou ) { unsigned int state; state = kou->x + kou->y; return state; } --------------------------------

  • List構造

    Listの尻にノードを追加する関数で困っています。 以下に、ソースの一部を掲載させていただきます。 typedef struct __node{ int data; struct __node *next; }Node; ... /*リストの尻にノードを追加する関数 * 引数: head. リストの先頭ノードのポインタ data. リストの尻に追加したいint型の変数*/ void Insert_Tail(Node *head, int data) { Node *ptr = head; if(ptr == NULL){ /*<ノードが存在しない時には追加されない>*/ /*領域の確保*/      head = (Node*)calloc(1,sizeof(Node)); /*データをセット*/ head->data = data; head->next = NULL; return ; }else{ /*<ノードが存在するときには正常に動作>*/ while(ptr->next != NULL){ ptr = ptr->next; } /*領域の確保*/ ptr->next = (Node*)calloc(1,sizeof(Node));      /*データのセット*/ ptr->next->data = data; ptr->next->next = NULL; } } コメントアウトにも書かせていただきましたが、ノードがすでに存在するときには、正常にノードの最後に追加してくれるのですが、ノードが存在しない時にはリストに追加してくれません。 どうかご指導、ご指摘の程お願いします。

  • 関数への構造体の配列の渡し方<c言語初心者>

    こんにちは、関数への構造体の配列の渡し方で理解できない点があるため、質問させていただきます。 以下がスクリプトになります。3人の名前と年齢をinput関数で入力し、それらのデータをoutput関数で出力するのが目的です。 #include <stdio.h> typedef struct{ char name[64]; int age; }property; void input(property *data[]); void output(property *data[]); int main(void){ property data[3]; printf("Input data of three people.\n"); input(&data); output(&data); return 0; } void input(property *data[]){ int i; for(i=0;i<3;i++){ printf("%d banme\n",i+1); printf("name:"); scanf("%s",&data[i]->name); printf(" age:"); scanf("%3d",&data[i]->age); } return; } void output(property *data[]){ int i; for(i=0;i<3;i++){ printf("%d banme\n",i+1); printf("name:%s\n",data[i]->name); printf("age :%3d\n",data[i]->age); } return; } コンパイル時のエラーメッセージは以下のようになりました。(ファイル名はstructure5.c) structure5.c: In function ‘main’: structure5.c:14:2: warning: passing argument 1 of ‘input’ from incompatible pointer type [enabled by default] input(&data); ^ structure5.c:8:6: note: expected ‘struct property **’ but argument is of type ‘struct property (*)[3]’ void input(property *data[]); ^ structure5.c:15:2: warning: passing argument 1 of ‘output’ from incompatible pointer type [enabled by default] output(&data); ^ structure5.c:9:6: note: expected ‘struct property **’ but argument is of type ‘struct property (*)[3]’ void output(property *data[]); ^ structure5.c: In function ‘input’: structure5.c:24:3: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[64]’ [-Wformat=] scanf("%s",&data[i]->name); ^ 構造体の配列をinput関数やoutput関数に渡すときにエラーが発生しているようなのですが、自分で調べても解決できなかったため、質問させて頂きます。 皆様のお知恵を貸してください。なおプログラミング言語自体初心者のため、できる限りわかりやすいお言葉でご教授願います。よろしくお願い致します。

  • 構造体のファイル書き込み&読み出しに関して2

    C言語を勉強しているものです。指定した番号に構造体を書き込み、指定した番号をの構造体を出力するプログラムを作成したいのですが、表示結果画像のようになってしまいます。 デバックしても、どこが違うのかがわかりません。説明不足かとは思いますがご教授お願いします。 ↓↓ソースコード↓↓ #include<stdio.h> #include<stdlib.h> struct S_data{ char Name[10+1];/*名前*/ int Sex;/*性別*/ int Height;/*身長*/ float Weight;/*体重*/ }; void FR_data(FILE *Fp,int pos); void FW_data(FILE *Fp,int pos); void OUP_data(struct S_data tag); void INP_data(struct S_data *tag); int RF_data(FILE *Fp,struct S_data *tag,int pos); int WF_data(FILE *Fp,struct S_data *tag,int pos); void main(){ FILE *Fp; int pos=0; int Ret; Fp=fopen("aaa.dat","r+b"); if(Fp==NULL){ Fp=fopen("aaa.dat","w+b"); if(Fp==NULL){ printf("File not open\n"); exit(2); } } while(1){ printf("入力の番号[0:終了]->"); scanf("%d",&pos); if(pos==0) break; FW_data(Fp,pos); } while(1){ printf("出力の番号[0:終了]->"); scanf("%d",&pos); if(pos==0) break; FR_data(Fp,pos); } Ret=fclose(Fp); } void FR_data(FILE *Fp,int pos){ struct S_data Temp; /*出力情報*/ int Ret; /*返却値*/ memset(&Temp,'\0',sizeof(Temp)); Ret=RF_data(Fp,&Temp,pos); /*情報の読み込み*/ if (Ret!=1){ printf("File not read\n"); }else{ OUP_data(Temp); /*情報の表示*/ } } void FW_data(FILE *Fp,int pos){ struct S_data wk; /*入力情報*/ int Ret; /*返却値*/ memset(&wk,'\0',sizeof(wk)); INP_data(&wk); /*情報の入力*/ Ret=WF_data(Fp,&wk,pos); /*情報の書き込み*/ if (Ret!=1){ printf("File not write\n"); } } void OUP_data(struct S_data tag){ printf("Name:%s\n",tag.Name); if (tag.Sex==0){ printf("Sex:M\n"); }else{ printf("Sex:F\n"); } printf("Height:%d\n",tag.Height); printf("Weight:%.2f\n",tag.Weight); } void INP_data(struct S_data *tag){ memset(tag,'\0',sizeof(tag)); printf("Name-->"); scanf("%s",&tag->Name); printf("Sex[0:M1:F]-->"); scanf("%d",&tag->Sex); printf("Height-->"); scanf("%d",&tag->Height); printf("Weight-->"); scanf("%f",&tag->Weight); } int RF_data(FILE *Fp,struct S_data *tag,int pos){ int Ret_I; /*fseek返却値*/ size_t Ret_S; /*fread返却値*/ Ret_I=fseek(Fp,sizeof(tag)*(pos),SEEK_SET); Ret_S=fread(tag,sizeof(tag),1,Fp); return Ret_S; } int WF_data(FILE *Fp,struct S_data *tag,int pos){ int Ret_I; /*fseek返却値*/ size_t Ret_S; /*fwrite返却値*/ Ret_I=fseek(Fp,sizeof(tag)*(pos),SEEK_SET); Ret_S=fwrite(tag,sizeof(tag),1,Fp); return Ret_S; }

  • C++ ポインタについて質問です

    c++ で三角形の周囲を計算で出したいのですが、エラーがでてしまいます。うまくpoint が機能していないようなのですがどこが悪いのでしょうか?? main.cppの中身 #ifdef TRIANGLE_DONE { // Define three points Point p1 = {1, 3}, p2 = {-2, -2}, p3 = {3, -1}; // Create a Triangle variable Triangle t = {p1, p2, p3}; // Now, test the perimeter() function assertDoubleEqualsMsg("Check perimeter of Triangle", 15.4021, perimeter(t), 1E-5); } #else cerr << "Define the Triangle structure. Then, uncomment #define TRIANGLE_DONE in h19.h" << endl; #endif hの中身 #define TRIANGLE_DONE struct Triangle{ Point a, b, c; }; #ifdef TRIANGLE_DONE /** * Calculates the perimeter of the Triangle t. * @param t the Triangle to examine. * @return the perimeter. */ double perimeter(const Triangle& t); #endif h19.cppのなかみ double premiter(const Triangle& t) { double p = 0; p = t.p1 + t.p2 + t.p3; return p; } 必ず "Define the Triangle structure. Then, uncomment #define TRIANGLE_DONE in h19.h" のコメントがでてしまいます。どのように改善すれば良いでしょうか?

  • 関数の掛け算を返す関数

    いま関数の積分を行おうとしています. この積分を行う関数が double integral(double (*func)(double), double a, double b ) {    double ans;    .....    return ans; } となっていて,aからbまでfuncが指す関数を積分して結果を返します. 積分をさせる関数は double f1(double x) { return 3.0*x; } double f2(double x) { return x*x; } となっていて,同じようにg1, g2も用意します.(本当は関数が3つ4つあります.) 例えばf1を積分したいとき, int main() {    double ans = integral(f1, a, b);    printf("%f\n", ans); } ですよね. 自分で積分する関数を選ぶときは,ここに配列で場合分けをして double (*func_f[])(double)={f1, f2}; scanf("%d", &flag); /* flagに0~1を代入 */ ans = integral(func_f[flag], a, b); でいいと思います. さて,そこで本題なのですが, double (*func_f[])(double)={f1, f2}; double (*func_g[])(double)={g1, g2}; としておいて, scanf("%d", flag1); scanf("%d", flag2); func_f[flag1], func_g[flag2]; として関数を入力側から決定して, (例えばflag1=1, flag2=1,であったとして *func_f[1]=x*x *func_g[1]=5.0*x ならば) h(x)=(x*x)*(5.0*x)=5.0*x*x*x という関数を作って, h(x)を指すポインタを double (*h_ptr)(double) とすれば ans = integral(h_ptr, a, b); としたいのです. (f1*g2)を一つの関数としてh(x)=(f1*g2)(x)というように扱うことができればいいと思うのですが. integralの引数で,関数のポインタを2個にすると,汎用性が失われてしまうと思うので,できればそこは変えたくないです. どのようにすればよいのでしょうか? また,「考え方を変えればよい」などの意見も頂きたいです. 皆様,どうぞよろしくお願いします.

専門家に質問してみよう