如何在PC / Visual C ++上找出指针是否在堆栈上

时间:2008-12-03 19:26:38

标签: c++ c visual-c++ pointers low-level

[这是专门针对PC / Visual C ++的(虽然其他任何答案都很有启发性):))

如何判断指针是否来自堆栈中的对象?例如:

int g_n = 0;

void F()
{
    int *pA = &s_n;
    ASSERT_IS_POINTER_ON_STACK(pA);
    int i = 0;
    int *pB = &i;
    ASSERT_IS_POINTER_ON_STACK(pB);
}

所以只有第二个断言(pB)应该跳闸。我正在考虑使用一些内联汇编来确定它是否在SS段寄存器中或类似的东西。有没有人知道是否有任何内置功能,或者这是一种简单的方法吗?

谢谢! RC

9 个答案:

答案 0 :(得分:2)

无论你做什么,它都将是非常特定于平台和不可移植的。假设你对此感到满意,请继续阅读。如果指针指向堆栈中的某个位置,它将位于当前堆栈指针%esp和堆栈顶部之间。

获得堆栈顶部的一种方法是在main()的开头读取它。但是,这有一些问题: - 堆栈的顶部实际上略高,因为C运行时在输入main()之前初始化堆栈 - 在C ++中,全局对象的构造函数在main()之前调用 - 如果您的应用程序是多线程的,则每个线程都有自己独立的堆栈。在这种情况下,您需要一个描述堆栈基础的线程局部变量

获取当前堆栈指针的一种方法是使用内联汇编:

uint32_t GetESP(void)
{
    uint32_t ret;
    asm
    {
        mov esp, ret
    }
    return ret;
}

谨防内联和优化!优化器可能会破坏此代码。

答案 1 :(得分:2)

我会回答这个问题 - 为什么你需要知道?没有好处可以来自这个。

我认为这个方法可能有用,如果编译器通过指针比较做了合理的事情并且堆栈增长了:

static void * markerTop = NULL;

int main()
{
    char topOfStack;
    markerTop = &topOfStack;
    ...
}

bool IsOnStack(void * p)
{
    char bottomOfStack;
    void * markerBottom = &bottomOfStack;
    return (p > markerBottom) && (p < markerTop);
}

答案 2 :(得分:1)

从技术上讲,在便携式C中你无法知道。参数堆栈是许多但不是所有编译器都遵守的硬件细节。有些编译器会尽可能使用寄存器作为参数(即fastcall)。

如果您专门在Windows NT上工作,则需要从调用NtCurrentTeb()中获取线程执行块。 Joe Duffy's blog有关于此的信息,您可以从中获得堆栈范围。你检查范围内的指针,你应该好好去。

答案 3 :(得分:1)

由于您指定了Visual C并断言,我将假设您可以使用调试版本。 在这种情况下,您可以利用此特定编译器用于内存检查的fenceposts:

#define IS_POINTER_TO_STACK(vp)   (*((int*)(vp)-1)==0xCCCCCCCC)

在所有这些情况下在调试版本中正常工作:

#define ASSERT(v)  printf("assert: %d\n", v);  //so it doesn't really quit
int g_n = 0;
void test_indirectly(void* vp) {
    ASSERT(IS_POINTER_TO_STACK(vp));
}
void F() {
    int *pA = &g_n;
    ASSERT(IS_POINTER_TO_STACK(pA));         //0

    int i = 0;
    int j = 0;
    int *pB = &i;
    ASSERT(IS_POINTER_TO_STACK(pB));         //1
    ASSERT(IS_POINTER_TO_STACK(&j));         //1

    int *pC = (int*)malloc(sizeof(int));
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    free(pC);
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    pC = new int;
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    delete pC;

    char* s = "HelloSO";
    char w[6];
    ASSERT(IS_POINTER_TO_STACK("CONSTANT")); //0
    ASSERT(IS_POINTER_TO_STACK(s));          //0
    ASSERT(IS_POINTER_TO_STACK(&w[0]));      //1
    test_indirectly(&s);                     //1

    int* pD; //uninit
    ASSERT(IS_POINTER_TO_STACK(pD));    //runtime error check

}

(除了最后一个由于未初始化的内存导致运行时错误 - 但仍然用于验证指针。)

这仅适用于Debug版本 - 版本构建报告所有版本都为false。

答案 4 :(得分:0)

忽略'why'的问题...如果你控制顶层堆栈框架的一个简单方法是将一个全局变量设置为堆栈对象的地址,然后使用一个函数来检查是否目标指针位于此地址与它在堆栈上创建的变量的地址之间。

void* TopOfStack; // someone must populate this in the first stack frame

bool IsOnTheStack(void* p)
{
  int x;

  return (size_t) p < (size_t) TopOfTheStack &&
         (size_t) p > (size_t) &x;
}

当然,这不适用于多个线程,除非你将TopOfTheStack线程设为本地。

编译器的堆栈优化也可能导致问题。

答案 5 :(得分:0)

是的我知道这是非常不可移植的,但这是一个内部应用程序模仿其他硬件的设施来做到这一点。似乎线程执行块可能是要走的路。

答案 6 :(得分:0)

我不相信Visual C ++或几乎任何在现代Windows(或古老的32位OS / 2)平台上运行的东西,因为在这些平台上,堆栈会动态增长,即堆栈的新页面被分配只有当你的程序试图访问所谓的防护页面时,才会在当前分配的堆栈块的顶部创建一个4 KB的块(对于32位Windows,无论如何)的特制内存。操作系统拦截程序尝试访问此保护页面时生成的异常,以及(1)在正常有效堆栈的新页面上映射代替它,在当前分配的堆栈顶部之上,以及(2)创建另一个防护页面位于新顶部的上方,因此可以根据需要增加堆栈。操作系统执行此操作直到堆栈达到其限制,并且通常将此限制设置得非常高。

如果您的程序试图访问属于堆栈未分配部分但位于防护页面之上的任何地址,则程序将崩溃,因为操作系统无法解释此问题。您的程序只是尝试访问其地址空间之外的内存,即使指针在理论上属于任务的堆栈段。

但是,如果你需要一种方法来查找一个地址是否属于堆栈的已分配部分(即“堆栈中的一个对象”),那么对Joe Duffy博客的引用是好的。只是不要使用那里描述的StackLimit,使用其他已经在此线程中描述的方法获取当前堆栈顶部,因此您对堆栈的已分配部分进行操作,而不是整个,可能是部分未分配的,一个

答案 7 :(得分:0)

我同意那些在不断调整堆栈大小的环境中难以可靠地做的人的看法。

但是因为'为什么?'人们 - 有趣的是我今天想在一个小型嵌入式平台上做这件事。我有一个函数,它接受一个对象的指针,然后在函数返回后保持该指针一段时间(因为它正在处理指针所指向的数据)。

我不希望我的函数的调用者传入自动变量的地址,因为我不希望数据在仍在处理时被踩踏。调用者必须传入静态数据或const数据的地址,并且一个好的'ASSERT_IS_ON_STACK()'宏可能是一个有用的提醒。

便携式?一点也不。糟糕的糖果机界面?绝对

这就是小型嵌入式系统的本质 - 良好的断言可以提供帮助。

答案 8 :(得分:0)

好的,关于“为什么”:

某些处理器的内存控制器无法执行DMA或将内存映射到堆栈段或从堆栈段映射内存;因此,在跨平台的世界中,为了确保我不从那里发送数据,跨平台断言非常有用。