• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:【C++】関数のアドレス)

C++関数のアドレスとクラスのポインタ

このQ&Aのポイント
  • C++のクラスで、メンバーのアドレスや関数の格納先について疑問があります。
  • ポインタ型のクラスに別のクラスを格納した場合、格納先の領域が不足しているのではと思っています。
  • しかし、実際にはコンパイルエラーもなく正常に動作するため、何が間違っているのか分かりません。

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

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

まず、「ポインタ」というのは、多くの場合(そうでない場合もある)同じサイズです。 なにせ、「ポインタ」であって、実体ではないのですから。 で、クラスの実体として考えた場合、 ・クラスのメンバデータは、それぞれのインスタンスの中にその実体を持ちます。 ・クラスのメンバ関数は、それぞれのインスタンスに、全く実体を持ちません(ポインタも含めて) class A a; a.finc(); という関数の呼び出しは、見かけ上、a というインスタンスの中に関数を呼び出しているように見えますが、単に、 finc(a.this); という、インスタンス a へのポインタを(暗黙の)引数として、普通に関数を呼び出しているだけです。 で、継承を使った場合、「そのインスタンスは、本当はどの関数を呼ぶのが正しいのか」を示す、「仮想関数テーブル」へのポインタを持ちます。 これも、持っているのは「ポインタ」だけなので、仮想関数テーブルがどんな大きさでも問題はありません。 さて、質問の中でお書きのように、基底クラス A のインスタンスが、100バイトで収まり、それを継承したクラス B のインスタンスが、300バイト必要な場合、多くの場合、C++の実装上、class A を表す構造体に、class B で必要なデータを「追加」するようなことをしています。 なので、「100バイトしかないAのインスタンスの『実体』に、300バイトのBのインスタンスの『実体』を代入する」ことはできません。 しかし、ポインタなら、「Aのインスタンスをポイントするポインタ」も、「Bのインスタンスをポイントするポインタ」も同じサイズです。(32bit 用のコンパイラなら、多くの場合どちらも32bit 幅) なので、ポインタ間の代入はできます。 たとえば、辞書(書籍の形になった辞書)を考えましょう。 ある項目は、10文字ほどの解説かもしれません。別の項目は、100文字でしっかり解説してあるかもしれません。 これを、「10文字の原稿用紙に書き写しなさい」と言われたら、前者は可能ですが、後者は不可能です。 しかし、「その項目は何ページにありますか?」だったら、どちらの項目も、(おそらく)4文字の原稿用紙でかけるでしょう。(ページ数のみだから) これが、ポインタに相当します。 そして、継承関係にあるクラスを代入できるのが、(実体ではなく)、ポインタと参照(これも、実際にはポインタです)に限られるのも、このあたりが関係しています。

TeferiMage
質問者

お礼

「仮想関数テーブル」へのポインタ、まだ理解は甘いですがもっと調べてみます。 辞書の目次上の各関数の「ページ番号」をイメージすると、 子供のクラスの関数の、コードセグメントの場所だけをそこに格納すればよいから、 親にメソッドa,b,cがあれば、 子のメソッドa,b,cのアドレス格納分だけの領域があればよく、 親クラスのポインタに、派生クラスのポインタを突っこんでも差し支えないようですね! また、逆に、子供独自のメソッドdは、そのアドレスを示すための領域がないから、実行できない? と理解しました。 ありがとうございます! .

その他の回答 (5)

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.6

>また、ポインタが、先頭アドレスしか持っていないとしたら、クラスAで使用しているメモリ領域の最後はどこなのか?や、 ポインタがクラスAのポインタという事がわかってるのであればクラスAのサイズからメモリ領域の最後はわかります。 >クラスAの中の、プロパティxの部分のためのメモリ領域はどこからどこまでなのか? メンバxのサイズはわかりますしクラスAでの位置もわかってるわけですから上記と同様にわかります。 あとCやC++には言語仕様としてプロパティーはないです。

TeferiMage
質問者

お礼

ありがとうございます。 メンバxのサイズや、クラスAのサイズがわかるために、 各メンバのメモリ領域の場所がわかるのですねー! .

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

ついでに: #2 のように class A { // 他のメンバは省略 public: void y(); private: int x; }; class B : public A { // ここも他のメンバは省略 public: void z(); private: int x; }; A *objA = new B; とした場合, objA から B::z はそもそも呼び出せません.

TeferiMage
質問者

お礼

ありがとうございます。 そうみたいですね。。ずっと勘違いしていました。 .

回答No.4

No.3 補足です。 プログラム言語を考える場合、「仕様」と「実装」は分けて考えないと混乱するとよく言われます。 たとえば、単純な構造体を考えてみましょう。 struct A { int a; double b; char c[1000]; }; この場合、c がどこから始まるか分からない? と不安になりますか? 実際には、コンパイラが、a, b, c のアドレスを割り当てますから、コンパイラは、a, b, c のアドレスが、A の先頭アドレスから、どれだけ離れているか知っています。 その情報を元に、コンパイルが行われるので問題ないのです。 あと、関数については、No.3 と重複しますが、「メンバ関数の実体やポインタ」は、(多くの実装では)インスタンスに含まれません。 class A { void func(); }; で、 A a; a.func(); とした場合、これは、インスタンス a が持っている情報を使って func を呼び出すのではなく、単に、 A::func(a.this); という呼び出しを行っているだけです。 呼び出された func() は、最初の(見えない)引数 a.this (a の先頭アドレス)を元に、自分は、インスタンス a を使うように呼び出されたのだな。 と知るだけです。

TeferiMage
質問者

お礼

関数は、インスタンスのメモリ領域ではなく、コードセグメントに入り、また恐らく、どのインスタンスから呼ばれたのか?を渡して、処理後にそのインスタンスの特定のメモリ領域を更新するようですね! ありがとうございます。

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

なんか、アドレスとかポインタとかごちゃごちゃ書かれてて、何が言いたいのかよくわからないんですけど。 できれば、サンプルコードで書いてください。 class A { int x ; void y(); } ; class childB : public A { int x ; void z(); } ; があったとして。 > Aクラスのポインタ型に、Aクラスを継承したChildBクラス(関数zを持つ)を格納したら、 というのは A * objA = new childB() ; ってことですか? もしそうなら。 元々、ポインタには(先頭)アドレスしか入っていません。

TeferiMage
質問者

お礼

ありがとうございます。 親と同じメソッドのアドレスがわかるが、 派生クラス独自のアドレスはわからないというところも含めておおむね理解できました!

TeferiMage
質問者

補足

ありがとうございます。 上のような状況です。 クラスAの先頭アドレスはポインタが示しているアドレスで良いので、なんら不思議はないのですが、 例えば、メソッドyの関数ポインタを宣言した場合、 「クラスAの先頭」しかわからないのだから、 メソッドyのアドレスがわかりようがないのでは?? なんでわかるのだろう?? というのが疑問です。 また、それどころか、メソッドzの関数ポインタを宣言しようとしたとして、 メソッドzは、クラスchildBで拡張されたものなので、 そもそもクラスAには、格納しようがないのでは? というのが不思議でなりません。 ////////////////////////////////////// また、ポインタが、先頭アドレスしか持っていないとしたら、クラスAで使用しているメモリ領域の最後はどこなのか?や、 クラスAの中の、プロパティxの部分のためのメモリ領域はどこからどこまでなのか? がわからないのでは? と思うのですが、どういう仕組みで各プロパティ・メソッドの格納場所を特定しているのでしょうか?

  • wormhole
  • ベストアンサー率28% (1626/5665)
回答No.1

>Aクラスの持つ、 >メンバーxのアドレスが100番地で、関数yのアドレスが200番地のとき、 >(イメージ図「■■■■■■■■■■★★★★★★★★★★」) >        ↑この辺が100番地  ↑この辺が200番地 関数yはそのようには格納されません。 また質問文を読む限りポインタを理解されているようには思えません。

TeferiMage
質問者

お礼

回答をヒントに調べてみました。 アドレス FFFFFFFF 側 :スタック領域(若いアドレスに向かって確保) ・・・            :ヒープ領域(若くない方に向かって確保) ・・・            :データセグメント アドレス 00000000側 :コードセグメント データセグメントには、静的変数や、グローバル変数が格納され、 コードセグメントには、プログラムのコードなどが格納される。 データセグメントと、コードセグメントは大きさ固定 のようですね。

TeferiMage
質問者

補足

関数yの手続き内容は、どのように格納されるのでしょうか?