• ベストアンサー

【C++】同じインスタンスの同じメンバにアクセスしているハズなのに、取得した値が異なる

C++Builder6.0を使った画面アプリです。1つのexeとしてビルドしています。 メンバ変数へのアクセス firstFormインスタンスのメンバ stNode.count を 次の2箇所からアクセスしています。 (正確にはfirstFormが持つ、stNode構造体メンバのcountにアクセス) (1)firstFormのメンバ関数から読み取る (2)firstFormから呼び出されるsecondFormのメンバ関数から読み取る なぜか(1)と(2)で読み取った値が異なります。 しかし値が変わるような処理はしていません。 firstForm, secondForm はそれぞれ、TFromから派生したクラスです。 -------------------------------------------------------------------- 1回目のアクセス。期待する値を取得できた。 this ( TForm から派生する firstFormのインスタンスのメンバ関数からのアクセス )の値 :01682E0C nnum = this->stNode.count; 004037C1 8B4508 mov eax,[ebp+0x08] //多分、thisポインタをeaxに格納 004037C4 8B90DC030000 mov edx,[eax+0x000003dc] //thisポインタのメンバである stNode.nodesuの中身をedxに格納 004037CA 899578FFFFFF mov [ebp-0x00000088],edx //edxの中身をローカル変数nnumに格納 EAX 01682E0C EDX 0000022D EBP 0012F8F0 -------------------------------------------------------------------- 2回目のアクセス。( TForm から派生する secondFormのインスタンスのメンバ関数からのアクセス ) secondForm のポインタはthisと同じである :01682E0C nnum = secondForm->stNode.count; 004072B7 8B0D409B4900 mov ecx,[__ectbl__ __LateVCLInit + 0x2C] 004072BD 8B01 mov eax,[ecx] 004072BF 8B90D0030000 mov edx,[eax+0x000003d0] 004072C5 895580 mov [ebp-0x80],edx ECX 0049A5EC EAX 01682E0C EDX 0177F280 ---------------------------------------------------------------------- どちらの場合もEAXにfirstFormへのポインタが格納されているようです。 なのに、メンバへのオフセット?が 1回目は 0x03dc , 2回目は 0x03d0 となり12バイトずれて います。(オフセットがやたら大きい気はします。) メモリの中身をデバッガで確認すると、確かに12バイトずれたところに欲しい値があります。 ソースコードを眺めてもさっぱりわかりません。 何を調べれば良いのかさっぱり分かりません。 解決できる気がしないのですが、いろいろ考えるのも勉強になると思うので 「こういう可能性もあるのでは?」「これを調べたら?」といったアドバイスなど頂けたら助かります。 #不可解な事象というのは往々にして、全然関係ない初歩的なことに原因があるものですが・・・今回もそうなんだろうか。

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

  • ベストアンサー
回答No.3

質問者さんのレベル的にありえないとは思うけど、 まさか多重継承してないよね? いや、なければいいんだ。うん。

hogejo
質問者

お礼

firstFormとsecondFormで、firstFormの各メンバのアドレスを比較してみたところ 所々、微妙に違っていました。 よくよく確認したところ、 secondFormがincludeしているfirstForm.hと firstFormがincludeしているfirstForm.hが別モノでした。 (A)では(X)ををincludeしているつもりでしたが (Y)をincludeしていました。 ※includeファイルの検索パスはプロジェクトで共通のはずなので 同じヘッダファイル名ならば同じ実ファイルを指すと思っていましたが 単純にそうとは言えないようでした。 C++BuilderのIDE画面で#includeの位置にリンクするファイルを開くと、 ・MyProject/firstFormではMyProject/firstForm.h が開く ・MyProject/secondFormではAnotherProject/firstForm.h 開く 上記のようになっていました。プリプロセスの結果も、それを反映したものになっています。 MyProject +--+(X) firstForm.h +firstForm.cpp( #include <MyProject の firstForm.h> ) +secondForm.h +(A) seconfForm.cpp ( #include <AnotherProject の firstForm.h> ) AnotherProject +--+(Y) firstForm.h +firstForm.cpp +secondForm.h +seconfForm.cpp ・firstFormとsecondFormが別々のモジュールになっていること ・firstFormの構造の宣言が.hにて行われていること ・同名のファイルを持つ別のプロジェクトが存在すること これらの情報がない限りは、妄想力がないと解決しようのない問題でした。 考えてくださった皆様には申し訳ありませんでした。 多分、プロジェクトをコピーして作ったときに紛れ込んだモノだと思います。 今となっては、「メンバのポインタがずれている」ならば、「クラスの定義が別々なんだろ」と思えますが、そのときは全く気がつきませんでした・・・ ありがとうございました。

hogejo
質問者

補足

多重継承で派生元の2つのクラスに同じ名前のメンバがいる、というケースのこと? その点は調べていませんでした・・・が、そもそも多重継承は使っていないので、この心配は無用なようです。(多重継承は使ったことがないので、少し考え込んでしまいました。) 回答ありがとうございます。 もう少し自分でも考えてみて、解決しなかったら適当に締め切りたいと思います。また何かありましたらよろしくお願い致します。

その他の回答 (2)

  • chie65535
  • ベストアンサー率43% (8520/19368)
回答No.2

「thisポインタは、現在実行しているメンバ関数の、オブジェクトを示す」のは理解していますか? これは「firstFormのメンバ関数に突入した瞬間にthisはfirstFormを指し、secondFormのメンバ関数に突入した瞬間にthisはsecondFormを指す」と言う事。 これは「今、自分はどこにいますか?」って聞いているようなもので、東京都に居れば答えは「東京都」になり、大阪市に居れば答えは「大阪市」になるのと同じ。 従って、1回目のアクセスの nnum = this->stNode.count; は nnum = firstForm->stNode.count; と同等。 これは、東京都に来て「中央区はどこですか?」と聞いているようなもので、この質問の答えは「東京都の中央区はここです」になる。そして「東京都中央区はどこですか?」と聞いているのと同等。 「firstForm->stNode.count」と「secondForm->stNode.count」は「実体が異なる、別々の変数」なのは自明。異なるオブジェクトの中身を見てるんだから、当たり前といえば当たり前。 同じ「中央区」でも「東京都中央区」と「大阪市中央区」は違う場所です。 従って nnum = firstForm->stNode.count; //東京都で「東京都中央区はどこですか?」と質問 nnum = secondForm->stNode.count; //大阪市で「大阪市中央区はどこですか?」と質問 が「異なる値になる」のと同様に nnum = this->stNode.count; //東京都で「中央区はどこですか?」と質問 nnum = secondForm->stNode.count; //大阪市で「大阪市中央区はどこですか?」と質問 が「異なる値になる」のは当然。 質問者さんがやりたい事は、多分、 1回目のアクセス(firstFormのメンバ関数からのアクセス) nnum = this->stNode.count; //東京都で「中央区はどこですか?」と質問 2回目のアクセス(secondFormのメンバ関数からのアクセス) nnum = firstForm->stNode.count; //大阪市で「東京都中央区はどこですか?」と質問 なのではなかろうか? firstFormのメンバ関数の中でthis->stNode.countにアクセス→東京都で「中央区はどこですか?」と質問→「東京都中央区はここ」が答え secondFormのメンバ関数の中でthis->stNode.countにアクセス→大阪市で「中央区はどこですか?」と質問→「大阪市中央区はここ」が答え firstFormのメンバ関数の中でfirstForm->stNode.countにアクセス→東京都で「東京都中央区はどこですか?」と質問→「東京都中央区はここ」が答え secondFormのメンバ関数の中でfirstForm->stNode.countにアクセス→大阪市で「東京都中央区はどこですか?」と質問→「東京都中央区はここ」が答え firstFormのメンバ関数の中でsecondForm->stNode.countにアクセス→東京都で「大阪市中央区はどこですか?」と質問→「大阪市中央区はここ」が答え secondFormのメンバ関数の中でsecondForm->stNode.countにアクセス→大阪市で「大阪市中央区はどこですか?」と質問→「大阪市中央区はここ」が答え

hogejo
質問者

お礼

丁寧にご回答くださりありがとうございました。

hogejo
質問者

補足

大変、申し訳ありません。 私の掲載したソースが間違っていました。 (掲載用に名称等を変えたのですが、間違っていました。) 正 : 2回目のアクセス nnum = firstForm->stNode.count; >1回目のアクセス(firstFormのメンバ関数からのアクセス) >nnum = this->stNode.count; //東京都で「中央区はどこですか?」と質>問 >2回目のアクセス(secondFormのメンバ関数からのアクセス) >nnum = firstForm->stNode.count; //大阪市で「東京都中央区はどこですか?」と質問 > >なのではなかろうか? おっしゃるとおり、そう意図しています。私の掲載時のミスで話がややこしくなりましたが、実際に問題がおきているソースでは secondFormのメンバ関数から firstFormのstNodeにアクセスしています。 ------------------------------- 改めてポインタについて説明致します。 firstFormでのthisの値 :01682E0C secondFormで確認した、firstForm のポインタは(firstFormの)thisと同じである :01682E0C 両者が同じ値であることから、同じオブジェクトを指していると思います。 アクセス時のオフセットが異なるところが不思議だと思っています。 1回目は 0x03dc , 2回目は 0x03d0 (もし2回目も0x03dcになっていれば、欲しい値にアクセスできるのですが・・・。) ------------------------------ objファイルを一旦全て削除して、ビルドしなおしましたが 結果は同じでした。 私の提供する情報が足りないかもしれませんが、ソースを全部出すわけにも行きませんし、、、

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

とりあえずソースがないことには話が進まないんだけど....

hogejo
質問者

お礼

回答ありがとうございます。 ソースを結構、量があり全部載せるわけにはいきませんので、 問題のメンバ変数へのアクセス部分だけを載せました。(というほどの量でもありませんが。)

関連するQ&A

  • システムコールについて。

    アセンブリ言語で、int $0x80はシステムコールと言われていますが、Linuxを知らないとわかりません。システムコールはアプリとカーネルのインターフェースです。 x86 Linux 32bitのシステムコールの呼び出しは int 0x80です。 システムコールはEAXに格納されている数値でいろいろな処理ができます。 https://www.mztn.org/lxasm64/x86_x64_table.html を見ていただくとWRITEのsyscall#は4です。 mov $0x4,%eax でeaxに4を入れているので画面に出力したいのだとわかります。 WRITEの第2引数は画面に出力したい文字列が格納されているアドレスでECXに格納します。 mov %esp,%ecx とスタックポインターのアドレスをecxに入れています。 ESPは push eax でEAXに格納されている$0x616b6157(Waka)がスタックに退避しています。 WRITEの第3引数は文字数です。文字数はEDXに格納します。 mov $0x4,%edx と4が入っているので文字数は4です。 このプログラムを実行させると画面にWakaと表示して元の画面に戻ります。そのためのRETです。 C言語で書けばたった1行。 write(1,"Waka",4) これについて詳しく教えていただけないでしょうか?すみません。

  • アセンブラの読み方

    お世話になります。 アセンブラを勉強しています。 下記について教えていただけないでしょうか。 よろしくお願いいたします。 1 .file 2 .data 3 var: .long 0x1234 4.text 5 .global main 6 main: 7 movb $1, %al // 値1をレジスタalに代入 8 push %eax // レジスタeaxの内容をスタックに格納 9 call IncReg // 関数呼び出し 10 pop %eax // スタックからレジスタeaxに引き出し 11 push var // 変数varの内容をスタックに格納 12 call IncReg // 関数呼び出し 13 pop %ecx // スタックからレジスタecxに引き出し 14 ret // リターン 15 IncReg: 16 movl %esp, %ebp // pushによって動いたスタックの先頭アドレスをレジスタ ebp に代入 17 movl 4(%ebp), %edx // mainから渡された eax(var) の値をレジスタ edx に代入 18 incw %edx // レジスタ edx の値を2増やす 19 movl %edx, 4(%ebp) // 2増やしたものを引数のあるスタックの場所に代入 20 ret // リターン (質問) 7 でレジスタ al に 1 を代入したのは何か意味があるのでしょうか。 17 の4(%ebp)はスタックポインタの1つ下、つまりmainから渡された引数でよろしいでしょうか。 18 の incw %edx はレジスタ edx の値を 2 増やすという意味でよろしいでしょうか。 13 でレジスタ ecx の値は 1236 になるので正しいでしょうか。

  • アセンブリ言語について。

    これが何をしているのか教えていただけないでしょうか?すみません。 これの事です。 (機械語データ) (アセンブリ言語) b8 57 61 6b 61 mov $0x616b6157,%eax 53 push %ebx 50 push %eax ba 04 00 00 00 mov $0x4,%edx bb 01 00 00 00 mov $0x1,%ebx b8 04 00 00 00 mov $0x4,%eax 89 e1 mov %esp,%ecx cd 80 int $0x80 58 pop %eax 31 c0 xor %eax,%eax 5b pop %ebx c3 ret

  • 機械語とアセンブリ言語について。

    (機械語データ) (アセンブリ言語) b8 57 61 6b 61 mov $0x616b6157,%eax 53 push %ebx 50 push %eax ba 04 00 00 00 mov $0x4,%edx bb 01 00 00 00 mov $0x1,%ebx b8 04 00 00 00 mov $0x4,%eax 89 e1 mov %esp,%ecx cd 80 int $0x80 58 pop %eax 31 c0 xor %eax,%eax 5b pop %ebx c3 ret こちらのアセンブリ言語の命令がわかるおすすめの書籍を知らないでしょうか? 教えていただけないでしょうか?すみません。

  • 機械語とアセンブリ言語について。

    (機械語データ) (アセンブリ言語) b8 57 61 6b 61 mov $0x616b6157,%eax 53 push %ebx 50 push %eax ba 04 00 00 00 mov $0x4,%edx bb 01 00 00 00 mov $0x1,%ebx b8 04 00 00 00 mov $0x4,%eax 89 e1 mov %esp,%ecx cd 80 int $0x80 58 pop %eax 31 c0 xor %eax,%eax 5b pop %ebx c3 ret で、右側のアセンブリ言語のニーモニックとオペランドについて解説していただけないでしょうか?すみません。

  • これらの文を意味のわかる分にしてください

    アドレス          ダンプ    逆アセコード 00551A90 31C9 XOR ECX,ECX 00551A92 51 PUSH ECX 00551A93 E8 68575000 CALL 00A57200 00551A98 59 POP ECX 00551A99 41 INC ECX 00551A9A 83F9 02 CMP ECX,2 00551A9D 75 F3 JNZ SHORT 00551A92 00551A9F 31C0 XOR EAX,EAX 00551AA1 C2 0400 RETN 4 00551AA4 90 NOP 00551AA5 ゞ 00551AA6 ゞ 00551AA7 ゞ 00551AA8 ゞ 00551AA9 90 NOP 00551AAA 8B4424 04 MOV EAX,[ESP+4] 00551AAE 8B48 04 MOV ECX,[EAX+4] 00551AB1 8B10 MOV EDX,[EAX] 00551AB3 8B40 08 MOV EAX,[EAX+8] 00551AB6 51 PUSH ECX 00551AB7 52 PUSH EDX 00551AB8 50 PUSH EAX 00551AB9 E8 12BB2B00 CALL 0080D5D0 00551ABE 83C4 0C ADD ESP,C 00551AC1 33C0 XOR EAX,EAX 00551AC3 C2 0400 RETN 4

  • 機械語に直すことについて。

    (機械語データ) (アセンブリ言語) b8 57 61 6b 61 mov $0x616b6157,%eax 53 push %ebx 50 push %eax ba 04 00 00 00 mov $0x4,%edx bb 01 00 00 00 mov $0x1,%ebx b8 04 00 00 00 mov $0x4,%eax 89 e1 mov %esp,%ecx cd 80 int $0x80 58 pop %eax 31 c0 xor %eax,%eax 5b pop %ebx c3 ret これのintと movとxor の機械語が分かりません。後、retの機械語が、farなのかnearなのかも分かりません。教えていただけないでしょうか?すみません。

  • c言語のワーニングを無くしたい

    c言語のダブルポインタ(char **)の引数渡しで困っています。 ワーニングが取れず、無視して実行するとセグメンテーション違反 となります。 ワーニングを取る方法をご存じないでしょうか? もしくは言語仕様、gcc仕様が要因でしょうか? バージョンはgcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)です。 001 void func(char **p1) 002 { 003 static char *s="AAA"; 004 **p1 = s; 005 } 006 int main(void) 007 { 008 char *q1; 009 func(&q1); 010 return(0); 011 } $ cc -Wall -o pp pp.c pp.c: In function ‘func’: pp.c:5: 警告: assignment makes integer from pointer without a cast 実行すると004行目で 'セグメンテーション違反です (core dumped)'となります。 004行目を *p1 = s; にするとワーニング無しとなり、 問題無くcall側のq1に先頭アドレスがセットされますが、 何か納得がいきません。 ちなみに、最初のcore吐きプログラムのアセンブルリストは 以下のようになります。 (*)位置で変なコードとなっていまが、ワーニングが出て いるので当然なのかもしれません。 (gdb) disass Dump of assembler code for function func: 0x08048354 <func+0>: push %ebp 0x08048355 <func+1>: mov %esp,%ebp 0x08048357 <func+3>: mov 0x8(%ebp),%eax 0x0804835a <func+6>: mov (%eax),%edx 0x0804835c <func+8>: mov 0x8049560,%eax *0x08048361 <func+13>: mov %al,(%edx) 0x08048363 <func+15>: pop %ebp 0x08048364 <func+16>: ret End of assembler dump. そして 004行目を *p1 = s;にした場合が以下となります。 想定通りのコードですが、何故なのかわかりません。 Dump of assembler code for function func: 0x08048354 <func+0>: push %ebp 0x08048355 <func+1>: mov %esp,%ebp 0x08048357 <func+3>: mov 0x8049560,%edx 0x0804835d <func+9>: mov 0x8(%ebp),%eax 0x08048360 <func+12>: mov %edx,(%eax) 0x08048362 <func+14>: pop %ebp 0x08048363 <func+15>: ret End of assembler dump.

  • アセンブリ言語について。

    >2進数の羅列では人間にはあまりに読み書きしにくいからです。 >それを命令単位に区切って、英単語を元にした名前を与えたのが、 >アセンブリ言語です。 ニーモニックとは、その命令の名前の事です。 簡潔に要点がまとまっている、すばらしい説明ですね。その通りです。 これで理解できないなら、追加の説明のしようがありません。 (なお、このように、他人の発言は引用符を付けて引用すると、わかりやすくなります。是非そうしてください。) まあ具体例を挙げるならこんな感じ。 (機械語データ) (アセンブリ言語) b8 57 61 6b 61 mov $0x616b6157,%eax 53 push %ebx 50 push %eax ba 04 00 00 00 mov $0x4,%edx bb 01 00 00 00 mov $0x1,%ebx b8 04 00 00 00 mov $0x4,%eax 89 e1 mov %esp,%ecx cd 80 int $0x80 58 pop %eax 31 c0 xor %eax,%eax 5b pop %ebx c3 ret 人間がCPUを直接動かすプログラムを書こうとするとき、アセンブリ言語を使って書きます(右側の部分)。 mov $0x616b6157,%eax push %ebx push %eax mov $0x4,%edx mov $0x1,%ebx mov $0x4,%eax mov %esp,%ecx int $0x80 pop %eax xor %eax,%eax pop %ebx ret そしてこれをアセンブルすると、1対1対応した機械語のデータ列が出来ます。 それが b8 57 61 6b 61 53 50 ba 04 00 00 00 bb 01 00 00 00 b8 04 00 00 00 89 e1 cd 80 58 31 c0 5b c3 この部分。 機械語には改行なんてありませんから、実際は一連の b8 57 61 6b 61 53 50 ba 04 00 00 00 bb 01 00 00 00 b8 04 00 00 00 89 e1 cd 80 58 31 c0 5b c3 と言うデータ列になります。 これをCPUが実行していきます。 相当熟練した人で無ければ、このデータ列を見るだけでプログラム構造を理解する、というわけに行きません。 なので、人間が機械語レベルでプログラミングする際には、アセンブリ言語を使います。 ※この回答のプログラムはこちらから引用しました。 >31バイトでつくるアセンブラプログラミング アセンブラ短歌の世界 >https://book.mynavi.jp/support/pc/4946/c01_assembra.pdf で、機械語データは、なぜ、0と 1だけではないのでしょうか?教えていただけないでしょうか?すみません。

  • x64移行でのインラインアセンブラ修正

    Win32のプログラムをx64に移行する仕事を頼まれました。 プラットフォームをx64に移してビルドを行ったところ、 インラインアセンブラを使用していた以下のコードでエラーが発生しました。 ~前略~ __asm { mov eax, 0; /* EAXに0を入れる */ cpuid; /* CPUID実行 */ mov dword ptr [vender_sig + 0], ebx; /* 最初の4文字 */ mov dword ptr [vender_sig + 4], edx; /* 次の4文字 */ mov dword ptr [vender_sig + 8], ecx; /* 最後の4文字 */ mov byte ptr [vender_sig + 12], 0; /* ラストに \0 */ } ~中略~ __asm { mov eax, 1; /* EAXに1を入れる */ cpuid; /* CPUID実行 */ mov dword ptr [prosessor_sig], eax; /* プロセッサシグネチャ */ mov dword ptr [function_flag], ebx; /* ファンクションフラグ */ mov dword ptr [feature_flag2], ecx; /* Featureフラグ */ mov dword ptr [feature_flag1], edx; /* Featureフラグ */ } ~中略~ __asm { mov eax, 1; cpuid; /* CPUID実行 */ mov dword ptr [t], eax; /* top */ mov eax, 3; cpuid; /* CPUID実行 */ mov dword ptr [m], edx; /* middle */ mov dword ptr [b], ecx; /* bottom */ } ~後略~ x64ではインラインアセンブラが使用できないということはわかったのですが、 これをどう直していいかがわかりません。 アドバイスよろしくお願い致します。

専門家に質問してみよう