确定指针是否有效

时间:2011-01-04 16:03:30

标签: c++ memory pointers free

我的观察是,如果调用free( ptr )ptr不是指向系统分配内存的有效指针,则会发生访问冲突。假设我这样称free

LPVOID ptr = (LPVOID)0x12345678;
free( ptr );

这肯定会导致访问冲突。有没有办法测试ptr指向的内存位置是否是有效的系统分配内存?

在我看来,Windows操作系统内核的内存管理部分必须知道已分配了哪些内存以及剩余的内存用于分配。否则,它怎么知道是否有足够的内存来满足给定的请求? (修辞)也就是说,似乎有理由断定必须有一个函数(或一组函数)允许用户确定指针是否是有效的系统分配内存。也许微软没有公开这些功能。如果微软没有提供这样的API,我只能假定它是出于故意和特定的原因。在系统中提供这样的钩子是否会对系统安全构成重大威胁?

情况报告

虽然知道内存指针是否有效在许多场景中都很有用,但这是我的特殊情况:

我正在为新硬件编写驱动程序,以替换通过USB连接到PC的现有硬件。我的任务是编写新的驱动程序,以便对当前驱动程序的现有API的调用将继续在使用它的PC应用程序中工作。因此,对现有应用程序唯一需要的更改是在启动时加载适当的驱动程序DLL。这里的问题是现有的驱动程序使用回调将接收到的串行消息发送给应用程序;指向包含消息的已分配内存的指针通过回调从驱动程序传递到应用程序。然后,应用程序负责调用另一个驱动程序API,通过将相同的指针从应用程序传递给驱动程序来释放内存。在这种情况下,第二个API无法确定应用程序是否实际传回了指向有效内存的指针。

13 个答案:

答案 0 :(得分:19)

实际上有一些名为IsBadReadPtr()IsBadWritePtr()IsBadStringPtr()IsBadCodePtr()的函数可能完成工作,但是{{3} }。我之所以提到这一点,是因为您知道这些选项值得追求。

你最好确保在指向任何内容时将所有指针设置为NULL0,然后再进行检查。

例如:

// Set ptr to zero right after deleting the pointee.
delete ptr; // It's okay to call delete on zero pointers, but it
            // certainly doesn't hurt to check.

注意:这可能是某些编译器(do not use it ever)的性能问题,因此首先对零进行自检可能是值得的。

ptr = 0;

// Set ptr to zero right after freeing the pointee.
if(ptr != 0)
{
    free(ptr); // According to Matteo Italia (see comments)
               // it's also okay to pass a zero pointer, but
               // again it doesn't hurt.
    ptr = 0;
}

// Initialize to zero right away if this won't take on a value for now.
void* ptr = 0;

更好的是使用see the section "Code Size" on this page的某些变体,而不必直接处理指针:

class Resource
{
public:
    // You can also use a factory pattern and make this constructor
    // private.
    Resource() : ptr(0)
    {
        ptr = malloc(42); // Or new[] or AcquiteArray() or something
        // Fill ptr buffer with some valid values
    }

    // Allow users to work directly with the resource, if applicable
    void* GetPtr() const { return ptr; }

    ~Resource()
    {
        if(ptr != 0)
        {
            free(ptr); // Or delete[] or ReleaseArray() or something

            // Assignment not actually necessary in this case since
            // the destructor is always the last thing that is called
            // on an object before it dies.
            ptr = 0;            
        }
    }

private:
    void* ptr;
};

或者如果适用,使用标准容器(这实际上是RAII的应用):

std::vector<char> arrayOfChars;

答案 1 :(得分:16)

简短回答:不。

在Windows中有一个函数,据说告诉你指针是指向真实内存(IsBadreadPtr()还是它的),但是 it doesn't work 你永远不应该使用它!

您的问题的真正解决方案是始终将指针初始化为NULL,并在delete之后将它们重置为NULL。

根据您的修改编辑:

你真的暗示了一个更大的问题:如果面对客户端代码搞砸了,你怎么能确保你的代码继续正常运行?

这真的应该是一个问题。没有简单的答案。但这取决于你的意思是“继续正常运作。”

有两种理论。有人说,即使客户端代码向您发送完整的废话,您也应该能够跋涉,丢弃垃圾并处理好的数据。实现这一目标的关键是异常处理。如果在处理客户端数据时遇到异常,请回滚状态并尝试返回,就好像他们根本没有打过你一样。

另一种理论是甚至不试图继续,而只是失败。失败可以是优雅的,并且应该包括一些全面的日志记录,以便可以识别问题并希望在实验室中修复。启动错误消息。告诉用户下次要尝试的一些事情。生成小型转储,并自动将它们发送回商店。但是,关闭。

我倾向于赞同第二种理论。当客户端代码开始发送垃圾时,系统的稳定性通常处于危险之中。他们可能已经破坏了堆。可能无法获得所需的资源。谁知道问题可能是什么。你可能会得到一些散布不好的好数据,但你甚至不知道好的数据是否真的好。因此,尽快关闭,以降低风险。

答案 2 :(得分:9)

不,你应该知道你的指针是否指向正确分配的内存。

答案 3 :(得分:9)

为了解决您的具体问题,我认为您不必担心检查指针。如果应用程序将DLL传递给无效地址,则表示应用程序中的内存管理问题。无论您如何编写驱动程序代码,都无法修复真正的错误。

为了帮助应用程序开发人员调试他们的问题,您可以向返回应用程序的对象添加magic number。调用库时释放对象,检查编号,如果不存在,则打印调试警告,不要释放它!即:

#define DATA_MAGIC 0x12345678
struct data {
    int foo;    /* The actual object data. */
    int magic;  /* Magic number for memory debugging. */
};

struct data *api_recv_data() {
    struct data *d = malloc(sizeof(*d));
    d->foo = whatever;
    d->magic = DATA_MAGIC;
    return d;
}

void api_free_data(struct data *d) {
    if (d->magic == DATA_MAGIC) {
        d->magic = 0;
        free(d);
    } else {
        fprintf(stderr, "api_free_data() asked to free invalid data %p\n", d);
    }
}

这只是一种调试技术。如果应用程序没有内存错误,这将正常工作。如果应用程序确实存在问题,那么可能会警告开发人员错误。它只能起作用,因为你的实际问题比初始问题所表明的要严格得多。

答案 4 :(得分:6)

没有。您应该只有一个指向您知道有效的内存的指针,通常是因为您在同一程序中分配了它。正确跟踪你的内存分配,然后你甚至不需要这个!

此外,您通过尝试free无效指针来调用未定义的行为,因此它可能会崩溃或做任何事情。

此外,free是从C继承的C ++标准库的函数,而不是WinAPI函数。

答案 5 :(得分:5)

首先,在标准中没有什么可以保证这样的事情(freemalloc ed指针是未定义的行为。)

无论如何,通过free传递只是试图访问该内存的扭曲路径;如果你想检查一个指针指向的内存是否在Windows上是可读/可写的,你真的应该尝试并准备好处理SEH异常;这实际上是IsBadxxxPtr函数通过在返回代码中转换此类异常所做的事情。

然而,这是一种隐藏微妙错误的方法,如this Raymond Chen's post中所述;所以,长话短说,没有没有安全的方法来确定指针是否指向有效的东西,我认为,如果你需要在某个地方进行这样的测试,那么代码中存在一些设计缺陷。

答案 6 :(得分:4)

我不会回应每个人已经说过的话,只是为了补充这些答案,这就是智能指针存在的原因 - 使用它们!

任何时候你发现自己因为内存错误而不得不解决崩溃问题 - 退一步,大呼吸,并解决潜在的问题 - 尝试解决它们是危险的!

根据您的更新进行编辑:

我有两种理智的方法可以做到这一点。

  1. 客户端应用程序提供了一个放置消息的缓冲区,这意味着您的API不必担心管理内存 - 这需要更改您的接口和客户端代码。
  2. 您更改了界面的语义,并强制界面的客户端不用担心内存管理(即您使用指针调用仅在回调的上下文中有效的内容 - 如果客户端需要,他们会他们自己的数据副本)。这不会改变您的界面 - 您仍然可以使用指针回调,但是您的客户需要检查他们是否在该环境之外不使用缓冲区 - 如果他们这样做,可能不是什么你想要什么,所以他们修理它可能是一件好事(?)
  3. 我个人会选择后者,只要你可以确定缓冲区不在回调之外使用。如果是,那么你将不得不使用hackery(例如已经建议使用幻数 - 虽然这并不总是保证有效,例如,让我们说某种形式的缓冲区溢出来自上一个块,你以某种方式用废话覆盖了幻数 - 那里会发生什么?)

答案 7 :(得分:2)

应用程序内存管理由应用程序开发人员来维护,而不是操作系统(即使在托管语言中,操作系统也不会执行该操作,垃圾收集器会这样做)。如果在堆上分配对象,则您有责任正确地释放它。如果您没有这样做,您的应用程序将泄漏内存。操作系统(至少在Windows的情况下)确实知道它为您的应用程序提供了多少内存,并在应用程序关闭(或崩溃)时回收它,但是没有记录的方式(可行)查询内存地址,看它是否是一个已分配的块。

我能给你的最佳建议:学会妥善管理你的记忆。

答案 8 :(得分:1)

不能无法访问malloc实现的内部。

你也许可以识别一些无效的指针(例如,那些指向进程虚拟内存空间中任何位置的指针),但是如果你使用一个有效的指针并向它添加1,它将无效,因为调用{{1但仍将指向系统分配的内存。 (更不用说在同一个指针上多次调用free()的常见问题)。

答案 9 :(得分:1)

除了其他人关于这是非常糟糕的做法的明显观点之外,我还看到了另一个问题。

仅仅因为某个地址不会导致free()生成访问冲突,并不意味着释放该内存是安全的。该地址实际上可能是堆中的地址,因此不会发生访问冲突,释放它会导致堆损坏。或者它甚至可能是一个有效的免费地址,在这种情况下你已经释放了一些可能仍在使用的内存块!

你真的没有解释为什么甚至应该考虑这种糟糕的做法。

答案 10 :(得分:1)

你显然已经确定你已经完成了一个当前有指针的对象,如果该对象是malloc,你想要free它。这听起来不是一个不合理的想法,但事实上你有一个指向对象的指针并没有告诉你关于如何分配该对象的任何信息(使用mallocnew,{ {1}},在堆栈上,作为共享内存,作为内存映射文件,作为APR memory pool,使用Boehm-Demers-Weiser garbage collector等,因此无法确定正确的方法取消分配对象(或者如果需要取消分配;您可能有一个指向堆栈上对象的指针)。这就是你实际问题的答案。

但有时最好回答应该问的问题。那个问题是“我怎么能用C ++来管理内存,如果我不能总是说'这个对象是如何分配的,以及它应该如何解除分配'?”这是一个棘手的问题,虽然这并不容易,但如果遵循一些政策,就可以管理内存。每当您听到有人抱怨每个new[]malloc正确配对时,每个freenew以及每个deletenew[]等配对,你知道,如果没有遵守严格的记忆管理制度,他们的生活就会变得更加艰难。

我要猜测你是否正在向函数传递指针,当函数完成时你想要它清理指针。这项政策通常无法做到正确。相反,我建议遵循以下策略:(1)如果函数从其他人获取指针,那么“其他人”应该清理(毕竟,“其他人”知道如何分配内存)和( 2)如果函数分配了一个对象,那么该函数的文档将说明应该使用什么方法来释放该对象。其次,我强烈推荐智能指针和类似的类。

Stroustrup's advice is

  

如果我创建10,000个对象并指向它们,我需要删除那些10,000个对象,而不是9,999,而不是10,001。我不知道该怎么做。如果我必须直接处理10,000个物体,我会搞砸了。 ......所以,很久以前我曾想过,“好吧,但我能正确处理少量物体。”如果我有一百个对象需要处理,我可以肯定我已经正确处理了100而不是99.如果我可以得到数字到10个对象,我开始变得快乐。我知道如何确保我已正确处理10而不仅仅是9。“

例如,您需要这样的代码:

delete[]

答案 11 :(得分:0)

为什么0x12345678必然无效?如果你的程序使用了大量内存,那么可以在那里分配一些东西。实际上,只有一个指针值应该完全依赖于无效分配:NULL。

答案 12 :(得分:0)

C ++不使用'malloc'而是'new',它通常具有不同的实现;因此'删除'和'免费'不能混合 - 也不能'删除'和'删除[]'(它的数组版本)。

DLL有自己的内存区域,不能与非DLL内存区域的内存管理系统混合使用。

每种API和语言都有自己的内存管理对象。 即:你没有'免费()'或'删除'打开的文件,你'关闭()'他们。对于其他API也是如此,即使该类型是指向内存而不是句柄的指针。