2

[这是专门针对 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 段寄存器或类似的东西中。有谁知道是否有任何内置功能,或者一个简单的方法来做到这一点?

谢谢!钢筋混凝土

4

9 回答 9

2

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

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

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

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

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

于 2008-12-03T19:36:46.150 回答
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);
}
于 2008-12-03T19:37:59.207 回答
1

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

如果您专门在 Windows NT 上工作,您希望通过调用 NtCurrentTeb() 来获取线程执行块。 Joe Duffy 的博客有这方面的信息,您可以从中获取堆栈范围。你检查范围内的指针,你应该很高兴。

于 2008-12-03T19:37:06.883 回答
1

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

#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 构建 - Release 构建对所有这些都报告错误。

于 2008-12-06T00:09:11.887 回答
0

忽略“为什么”的问题......如果您可以控制顶部堆栈帧,一种简单的方法是将全局变量设置为堆栈对象的地址,然后使用一个函数来检查目标指针是否为在这个地址和它在堆栈上创建的变量的地址之间。

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 线程设为本地。

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

于 2008-12-03T19:37:19.863 回答
0

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

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

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

于 2008-12-03T21:14:00.993 回答
0

我同意那些说在不断调整堆栈大小的环境中很难可靠地做到这一点的人。

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

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

便携的?一点也不。可怕的糖果机界面?绝对地。

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

于 2008-12-03T21:29:24.113 回答
-1

是的,我知道这是非常不可移植的,但这是为了让内部应用程序模仿其他硬件的功能来执行此操作。似乎线程执行块可能是要走的路。

于 2008-12-03T20:02:41.187 回答
-1

好的,至于“为什么”:

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

于 2008-12-04T01:42:30.390 回答