2012-09-30 22 views
9

は、我々は出力として(私がテストした)C++はどのように関数とオブジェクトをメモリに格納しますか?

class A 
{ 
    int x; 
public: 
    void sayHi() 
    { 
     cout<<"Hi"; 
    } 
}; 

int main() 
{ 
    A *a=NULL; 
    a->sayHi(); 
} 

上記のコードは、ターボCにコンパイルされたクラスや印刷Hiを考えてみましょう。

aNULLなので、クラッシュが予想されました。もっとオーバー私はsayHi()機能は、仮想作れば、それは

Abnormal temination(Segmentation fault in gcc) 

言う私はそれの多くは実装依存である知っているが、誰がどのような実装にいくつかの光を投げたり、単にそれが本当にいいだろう概観を与えることができれば。

+2

ヌルポインタによるメソッドの呼び出しは、未定義の動作です。何かが起こる可能性があります。クラッシュする必要はありませんが、標準では許可されています。 –

+0

C++の人ではないので、これは推測ですが、あなたのコードは 'A'のインスタンスのメモリにアクセスする必要はありません。 'sayHi()'はフィールド 'x'を使用せず、仮想ではないので解決するためにvtableにアクセスする必要はありません。 C++コンパイラは実際には 'a'がエラーを引き起こす有効なポインタであるかどうかを確認するためにチェックを挿入する必要があります。 – millimoose

答えて

6

C++では、クラスのメソッドはそのクラスのインスタンス内に格納されません。それらは単にプログラマが指定した引数に加えてthisポインタを透過的に受け入れるいくつかの「特別な」関数です。

sayHi()メソッドはクラスフィールドのいずれも参照しないため、thisポインタ(NULL)は決して追跡されません。

これは間違いありませんが、これはまだ未定義の動作です。プログラムを呼び出すと、あなたの連絡先リストに厄介な電子メールを送信することがあります。この特定の例では、それは最悪のことを行い、動作するように見えます。

virtual私はこの質問に答えてからメソッドのケースが追加されましたが、他の人の回答に含まれているので、私は答えを洗練しません。

+1

+1 "動作する"という未定義の動作が悪いと指摘しています。 –

7

明らかに、コードには未定義の動作があります。つまり、取得するものはすべて偶然です。つまり、システムは非仮想メンバ関数を呼び出すときにオブジェクトについて知る必要はありません。シグネチャに基づいて呼び出すことができます。さらに、メンバ関数がメンバにアクセスする必要がない場合、実際にオブジェクトを必要とせず、実行するだけでよい。これは、コードが出力を出力したときに観察されたものです。しかし、これがシステムの実装方法であるかどうかは定義されていません。

仮想関数型システムを呼び出すと、オブジェクトに関連付けられている型情報レコードが表示されます。 NULLポインターで仮想関数を呼び出すと、そのような情報は存在せず、アクセスしようとするとクラッシュする可能性があります。それでも、そうする必要はありませんが、ほとんどのシステムでそうです。

BTW、main()常にintを返します。

1

クラスの非仮想メソッドを呼び出す場合、コンパイラのために関数がどのクラスに属しているかを知るだけで十分です。逆参照によって、メソッドを呼び出すクラスへのNULLポインタですが、コンパイラはその情報。 sayHi()メソッドは、クラスインスタンスへのポインタを隠しパラメータとして取る関数にすぎません。このポインタはNULLですが、メソッド内のどの属性も参照していなければ問題ありません。

このメソッドを仮想化すると、状況が変わります。コンパイラは、コンパイル時にどのコードがメソッドに関連付けられているか分からず、実行時にそれを把握する必要があります。それは基本的にすべての仮想メソッドのための関数ポインタを含むテーブルを調べることです。このテーブルはクラスインスタンスに関連付けられているため、NULLポインタを基準にしたメモリの部分を参照するため、この場合はクラッシュします。一般化として

3

、無スーパークラスと仮想関数でクラスからインスタンス化されたオブジェクトのレイアウトは、以下の通りである:

* - v_ptr ---> * pTypeInfo 
|    |- pVirtualFuncA 
|    |- pVirtualFuncB 
|- MemberVariableA 
|- MemberVariableB 

v_ptrは、Vテーブルへのポインタである - 仮想のアドレスを含みますオブジェクトのRTTIデータを取得します。仮想関数のないクラスには、v-tableはありません。

上記の例では、class Aには仮想メソッドがないため、vテーブルはありません。これは、呼び出しのためのsayHi()の実装がコンパイル時に決定され、不変であることを意味します。

コンパイラは、暗黙的なthisポインタをaに設定し、次にsayHi()の先頭にジャンプするコードを生成します。実装はオブジェクトの内容を必要としないので、ポインタがNULLのときに動作するという事実はうれしい偶然です。

sayHi()を仮想化する場合、コンパイラはコンパイラ時に呼び出す実装を判断できないため、代わりにvテーブルの関数のアドレスを検索して呼び出すコードを生成します。あなたの例では、aNULLである場合、コンパイラはアドレス0の内容を読み取り、中断を引き起こします。

+0

クラスAに仮想メソッドがないという事実は関係ありません。重要なことは、sayHiという特定の関数が仮想関数ではなく、クラスオブジェクトのメンバーを使用していないことです。したがって、この関数は、クラスオブジェクトへのポインタを使用せずに呼び出すことができます。 –

関連する問題