web-dev-qa-db-ja.com

C ++クラスは、スタックとヒープのどちらにあるかを判別できますか?

私が持っています

class Foo {
....
}

Fooが分離できるようにする方法はありますか?

function blah() {
  Foo foo; // on the stack
}

そして

function blah() {
  Foo foo* = new Foo(); // on the heap
}

スタックとヒープのどちらに割り当てられているかに応じて、Fooがさまざまなことを実行できるようにしたいと思います。

編集:

多くの人が私に「なぜこれをするのか」と尋ねました。

答え:

現在、参照カウントされたGCを使用しています。ただし、マーク&スイープも実行できるようにしたいです。このために、「ルート」ポインターのセットにタグを付ける必要があります。これらはスタック上のポインターです。したがって、クラスごとに、それらがスタックにあるのかヒープにあるのかを知りたいと思います。

38
anon

実際に私たちに本当の質問をする必要があります:-)なぜこれが必要だと思うのかは明らかですが、ほぼ確実にそうではありません。実際、それはほとんどの場合悪い考えです。

なぜあなたはこれをする必要があると思いますか?

私は通常、開発者がオブジェクトが割り当てられた場所に基づいてオブジェクトを削除するか削除したくないためだと思いますが、それは通常、コード自体ではなく、コードのクライアントに任せるべきものです。


更新:

申し訳ありませんが、あなたはおそらくあなたが求めていることが理にかなっている数少ない分野の1つを見つけたでしょう。理想的には、すべてのメモリ割り当て演算子と割り当て解除演算子をオーバーライドして、ヒープに作成されたものとヒープから削除されたものを追跡します。

ただし、deleteが呼び出されない状況が発生する可能性があり、マーク/スイープが参照カウントに依存しているため、クラスのnew/deleteをインターセプトするだけの単純な問題かどうかはわかりません。ポインタの割り当てをインターセプトして正しく機能させることができます。

あなたはそれをどのように扱うつもりかについて考えましたか?

古典的な例:

myobject *x = new xclass();
x = 0;

削除呼び出しは発生しません。

また、インスタンスの1つへのpointerがスタック上にあるという事実をどのように検出しますか? newとdeleteをインターセプトすると、オブジェクト自体がスタックベースかヒープベースかを保存できますが、特に次のようなコードでは、ポインターがどこに割り当てられるかをどのように判断するかがわかりません。

myobject *x1 = new xclass();  // yes, calls new.
myobject *x2 = x;             // no, it doesn't.
10
paxdiablo

それを行うためのハッキーな方法:

struct Detect {
   Detect() {
      int i;
      check(&i);
   }

private:
   void check(int *i) {
      int j;
      if ((i < &j) == ((void*)this < (void*)&j))
         std::cout << "Stack" << std::endl;
      else
         std::cout << "Heap" << std::endl;
   }
};

オブジェクトがスタック上に作成された場合、それは外部関数のスタック変数の方向のどこかに存在する必要があります。ヒープは通常、反対側から成長するため、スタックとヒープは中央のどこかで出会うことになります。

(これが機能しないシステムは確かにあります)

18
sth

答えはノーです。これを行うための標準的/ポータブルな方法はありません。新しい演算子のオーバーロードを伴うハックには、穴ができる傾向があります。ポインタアドレスのチェックに依存するハックは、OS固有およびヒープ実装固有であり、OSの将来のバージョンで変更される可能性があります。あなたはそれに満足しているかもしれませんが、私はこの振る舞いを中心にいかなる種類のシステムも構築しません。

私はあなたの目標を達成するためのさまざまな方法を検討し始めます-おそらくあなたはあなたのスキームの「ルート」として機能するために完全に異なるタイプを持つことができます、またはユーザーに特別なコンストラクターでスタックに割り当てられたタイプに(適切に)注釈を付けるように要求することができます。

8
Terry Mahaffey

より直接的で邪魔にならない方法は、メモリ領域マップ(/proc/<pid>/mapsなど)でポインタを検索することです。各スレッドには、そのスタックに割り当てられた領域があります。静的変数とグローバル変数は 。bssセクション に、定数はrodataまたはconstセグメントに存在します。

4
Matt Joiner

'this'の値をスタックポインタの現在の値と比較すると可能です。この<spの場合、スタックに割り当てられています。

これを試してみてください(x86-64でgccを使用):

#include <iostream>

class A
{
public:
    A()
    {
        int x;

        asm("movq %1, %%rax;"
            "cmpq %%rsp, %%rax;"
            "jbe Heap;"
            "movl $1,%0;"
            "jmp Done;"
            "Heap:"
            "movl $0,%0;"
            "Done:"
            : "=r" (x)
            : "r" (this)
            );

        std::cout << ( x ? " Stack " : " Heap " )  << std::endl; 
    }
};

class B
{
private:
    A a;
};

int main()
{
    A a;
    A *b = new A;
    A c;
    B x;
    B *y = new B;
    return 0;
}

次のように出力されます。

Stack 
Heap 
Stack 
Stack 
Heap
4
Gianni

私はあなたが何を求めているのか肯定的ではありませんが、new演算子をオーバーライドすることがあなたがやろうとしていることかもしれません。 C++でヒープ上にオブジェクトを作成する唯一の安全な方法は、new演算子を使用することであるため、ヒープ上に存在するオブジェクトと他の形式のメモリを区別できます。詳細については、Googleの「c ++の新しいオーバーロード」を参照してください。

ただし、クラス内から2つのタイプのメモリを区別することが本当に必要かどうかを検討する必要があります。オブジェクトを保存する場所によって動作が異なるように注意しないと、災害のレシピのように聞こえます。

3
Michael Koval

上記のように、オーバーロードされたnew演算子を使用してオブジェクトを割り当てる方法を制御する必要があります。ただし、2つの点に注意してください。最初に、ユーザーによって事前に割り当てられたメモリバッファ内でオブジェクトを初期化する「placementnew」演算子。次に、ユーザーが任意のメモリバッファをオブジェクトタイプにキャストすることを妨げるものは何もありません。

char buf[0xff]; (Foo*)buf;

もう1つの方法は、ほとんどのランタイムがヒープ割り当てを行うときに要求されるよりも少し多くのメモリを使用するという事実です。彼らは通常、ポインタによって適切な割り当て解除を識別するために、そこにいくつかのサービス構造を配置します。あなたはcouldランタイム実装でこれらのパターンを検査しますが、コードはreally移植不可能で、危険で、サポートできないやり過ぎになります。

繰り返しになりますが、前述のように、このソリューションを考案した最初の問題(「理由」)について尋ねる必要がある場合は、実際にソリューションの詳細(「方法」)を求めています。

3
Inso Reiges

いいえ、それは確実にまたは賢明に行うことはできません。

newをオーバーロードすることで、オブジェクトがnewで割り当てられたことを検出できる場合があります。

しかし、オブジェクトがクラスメンバーとして構築され、所有するクラスがヒープに割り当てられている場合はどうなるでしょうか。

これがあなたが持っている2つに追加する3番目のコード例です:

class blah {
  Foo foo; // on the stack? Heap? Depends on where the 'blah' is allocated.
};

静的/グローバルオブジェクトはどうですか?スタック/ヒープのものとどのように区別しますか?

オブジェクトのアドレスを調べ、それを使用して、スタックを定義する範囲内にあるかどうかを判断できます。ただし、スタックは実行時にサイズ変更される場合があります。

本当に、最良の答えは、「マーク&スイープGCがC++で使用されない理由理由がある」ということです。適切なガベージコレクタが必要な場合は、それをサポートする別の言語を使用してください。

一方、ほとんどの経験豊富なC++プログラマーは、リソース管理に必要な手法を学ぶと、ガベージコレクターのneedがほとんどなくなることに気付きます(- [〜#〜] raii [〜#〜] )。

2
jalf

MFCクラスの方法:

。H

class CTestNEW : public CObject
{
public:
    bool m_bHasToBeDeleted;
    __declspec(thread) static void* m_lastAllocated;
public:
#ifdef _DEBUG
    static void* operator new(size_t size, LPCSTR file, int line) { return internalNew(size, file, line); }
    static void operator delete(void* pData, LPCSTR file, int line) { internalDelete(pData, file, line); }
#else
    static void* operator new(size_t size) { return internalNew(size); }
    static void operator delete(void* pData) { internalDelete(pData); }
#endif
public:
    CTestNEW();
public:
#ifdef _DEBUG
    static void* internalNew(size_t size, LPCSTR file, int line)
    {
        CTestNEW* ret = (CTestNEW*)::operator new(size, file, line);
        m_lastAllocated = ret;
        return ret;
    }

    static void internalDelete(void* pData, LPCSTR file, int line)
    {
        ::operator delete(pData, file, line);
    }
#else
    static void* internalNew(size_t size)
    {
        CTestNEW* ret = (CTestNEW*)::operator new(size);
        return ret;
    }

    static void internalDelete(void* pData)
    {
        ::operator delete(pData);
    }
#endif
};

。CPP

#include "stdafx.h"
.
.
.
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

void* CTestNEW::m_lastAllocated = NULL;
CTestNEW::CTestNEW()
{
    m_bHasToBeDeleted = (this == m_lastAllocated);
    m_lastAllocated = NULL;
}
2
ggo

代わりにスマートポインタを使用することをお勧めします。設計上、クラスにはクラスに関するデータと情報が含まれている必要があります。簿記のタスクはクラスの外に委任する必要があります。

newとdeleteをオーバーロードすると、想像以上に多くの穴が発生する可能性があります。

1
Arohi

クラスのnew()をオーバーロードします。このようにして、ヒープとスタックの割り当てを区別できますが、スタックと静的/グローバルを区別することはできません。

1
Seva Alekseyev

あなたの質問に答えるために、信頼できる方法(あなたのアプリケーションが複数のスレッドを使用していないと仮定して)、あなたのスマートポインターに含まれていないすべてのものがヒープ上にないことを前提としています:

-> newをオーバーロードして、割り当てられたすべてのブロックのリストを各ブロックのサイズとともに保存できるようにします。 ->スマートポインタのコンストラクタが、このポインタが属するブロックを検索します。それがどのブロックにもない場合、それは「スタック上にある」と言うことができます(実際には、それはあなたによって管理されていないことを意味します)。それ以外の場合は、ポインタがいつどこに割り当てられたかがわかります(孤立したポインタや、メモリの空き容量などを探したくない場合)。アーキテクチャに依存しません。

1
Ano

Paxが尋ねるメタ質問は、「なぜそれをしたいのか」と尋ねられるので、より有益な回答が得られる可能性があります。

ここで、「正当な理由」(おそらく単に好奇心)のためにこれを行っていると仮定すると、演算子newとdeleteをオーバーライドすることでこの動作を得ることができますが、オーバーライドすることを忘れないでくださいall以下を含む12のバリアント:

new、delete、new no throw、delete no throw、new array、delete array、new array no throw、delete array no throw、placement new、placement delete、placement new array、placement deletearray。

できることの1つは、これを基本クラスに入れて派生させることです。

これは一種の苦痛ですが、どのような異なる行動を望みましたか?

1
Rick

解決策はありますが、それは継承を強制します。 Meyers、「より効果的なC++」、アイテム27を参照してください。

(あなたはこのリンクでそれを見ることができます:
http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI27_FR.HTM

0
HeyJude

ここでプログラムを見てください: http://alumni.cs.ucr.edu/~saha/stuff/memaddr.html 。いくつかのキャストで、それは出力します:

        Address of main: 0x401090
        Address of afunc: 0x401204
Stack Locations:
        Stack level 1: address of stack_var: 0x28ac34
        Stack level 2: address of stack_var: 0x28ac14
        Start of alloca()'ed array: 0x28ac20
        End of alloca()'ed array: 0x28ac3f
Data Locations:
        Address of data_var: 0x402000
BSS Locations:
        Address of bss_var: 0x403000
Heap Locations:
        Initial end of heap: 0x20050000
        New end of heap: 0x20050020
        Final end of heap: 0x20050010
0
Ray Tayek