【问题标题】:C++ - Check if pointer is pointing to valid memory (Can't use NULL checks here)C++ - 检查指针是否指向有效内存(此处不能使用 NULL 检查)
【发布时间】:2012-08-03 00:22:28
【问题描述】:

我正在创建脚本语言。 当我分配东西时,它分配了东西并返回地址 然后我用它做任何事情然后删除它。我无法控制其中的变量 就像在我的 lang 中创建结构(使用指针和 bool 来检查指针是否指向有效数据的结构)等等,因为它会使我的 lang 在 RAM 中变得更慢和更大。

例如:(我的脚本语言很容易理解。我怀疑你不会明白,但我还是会在里面放一些cmets)

MyStruct = { //Function. For create object with it use 'new' before it.
    TestAliveVar=0
}
Func = { //I'll explain what exactly this function does every place it runs.
    if (!exists(arg0)) //C++: ???
        exit;
    arg0.TestAliveVar=1
    println "Still alive!";
}
var MyVar=new MyStruct(); //Returns address of the new object in the heap
                          //and runs on it the `MyStruct` function.
Func(MyVar);              //Sets his 'TestAliveVar' to 1
                          //and prints 'Still Alive!' with new line
delete(MyVar);            //C++: free(MyVar);
Func(MyVar);              //Does nothing

问题是如何创建您在这段代码中看到的函数exists。 顺便说一句,我可以在这个语言中运行 C++ 代码。

【问题讨论】:

  • 很难想象保持“有效”标志可能比测试指针的有效性要慢。
  • @Ugo 如果我在没有 struct 的情况下将指针发送到代码中的任何地方,并将其删除,那么我如何检查指针是否仍然有效?
  • @BenVoigt 如果我考虑如何在我的语言中实现这一点。它将适合任何表达式的值记忆等等。顺便说一句,我在我的语言中的价值观也很大,我认为我不需要让它变得更大。
  • 我的意思是检查指针是否“有效”会产生很多误报,而且速度也非常慢。即使您对结构进行性能打击,这仍然比您询问的设计要快。

标签: c++ pointers null interpreter


【解决方案1】:

您可以使用shared_ptr<> 来保存您的指针,并使用weak_ptr<> 将您的指针传递给对象的消费者。你通过销毁shared_ptr<>对象来delete对象,然后所有的weak_ptr<>s都会过期。

std::weak_ptr<int> wptr;
assert(wptr.expired());
{
    std::shared_ptr<int> intptr(new int);
    wptr = intptr;
    assert(!wptr.expired());
}
assert(wptr.expired());

因此,您的exists 检查将是检查weak_ptr&lt;&gt; 是否已过期。

为了使构造的使用更加具体:

Script code                 Hypothetical C++ code that gets executed
----                        ----
var MyVar=new MyStruct();   var_map["MyVar"]
                                = std::shared_ptr<Obj>(new Obj("MyStruct"));
Func(MyVar);                invoke("Func", std::weak_ptr<Obj>(var_map["MyVar"]));
exists(arg0)                !args[0].expired()
delete(MyVar);              var_map.erase("MyVar");

如果脚本要在多线程环境中运行,那么weak_ptr&lt;&gt; 状态就是临界区。

【讨论】:

  • 我猜想(并且可能是错误的)他正在寻找一种解决方案,以保护他的解释器不访问从指向无效内存的脚本传递的指针...
  • 就像 VladLazarenko 所说的那样。
  • @VladLazarenko:我不明白。我认为我的解决方案与您的陈述之间没有任何不相容之处。
  • 我会提到expired 给出有意义的肯定,但可能给出假否定。
  • 抱歉,每次都要检查值是否是指针等,非常昂贵。
【解决方案2】:

可以检测内存是否不再存在,例如通过维护一组已知的死指针。您创建的任何指针都会添加到 alive 集合中,当您删除对象时,会将指针移动到 dead 集合中。

真正棘手的部分是重用内存。当您想为不同的对象重复使用相同的地址时,您会怎么做?你不能通过看指针来判断,因为指针看起来是一样的。因此,除非您永远不想重用内存,否则您将不得不更改您的要求。

  1. 一种可能的方法是在结构中添加字段。我知道您说过您不希望这样,但许多 cmets 已经建议这是最佳解决方案,我倾向于同意。
  2. 另一种可能的方法是增加一个间接层,这样您就不会真正传递指向对象的指针,而是将索引传递到活对象列表或其他内容中。
  3. 您还可以考虑引用计数和垃圾回收。这样,对象只会在没有人再引用它们时被删除。相当多的工作,但作为脚本语言的用户,我希望它能够提供垃圾收集。

【讨论】:

  • 我猜你需要同时存储指针和内存宽度。因为该范围内的任何内容都是有效的。例如,如果他的脚本正在传递一个指向有效数组中间元素的指针。但是,是的,+1
  • @VladLazarenko:仅当脚本可以进行指针运算时才需要宽度。如果数组访问,例如被包裹在不透明的函数中,然后知道整个数组是否还活着就足够了。
  • 我能得到的最接近的是第三个。但要实现它,需要大量的性能。表达式中的任何“pop”(我的表达式计算器有堆栈)都需要放入智能指针代码。
  • 这种方法的任何实现都有未定义的行为,即使内存从未被重用:指向已释放内存的指针值甚至无法进行可移植比较。
【解决方案3】:

这是一个非常糟糕的主意。指针是否安全使用不仅仅基于指针的值,它基于指针的整个历史。例如,假设您分配了一些内存,然后释放它,但您保留指向它的指针。该指针现在无效。现在其他东西被分配到前一个指针所在的内存。现在,如果您尝试检测旧指针是否有效,它似乎是有效的,因为它指向的内存已分配,但如果您尝试使用它,您会得到未定义的行为。如果你读它,你会得到垃圾。如果您尝试写入它,您可能会损坏堆。

如果您只想检测您的进程是否可以访问指针指向的内存,这是可能的但不可移植,绝对不是一个好主意(它也会很慢)。您基本上必须尝试读取或写入它,然后捕获操作系统异常或发出信号。正如我所说,即使这是一个非常糟糕的主意。它告诉你的是,如果你试图访问它,操作系统是否会杀死你的进程;不是它是否真的可以安全使用。

有关为什么这是一个坏主意的更多信息,请查看 Raymond Chen 的这些博客文章,他从事一些 Windows 的低级工作:

IsBadXxxPtr should really be called CrashProgramRandomly

There's no point improving the implementation of a bad idea

【讨论】:

    【解决方案4】:

    您可以做的一件事是使用自己的分配器,您可以在其中分配所有对象。您的exists 函数将简单地查询分配器以查看对象是否仍被分配。你可以使用类似于 SMR 的东西来确保您的引用在仍在使用时不会指向其他东西......

    【讨论】:

      【解决方案5】:

      最简单的解决方案是使用地图。映射应该由指向对象的指针索引,可能是void *。地图项的内容应该是创建的对象的类型。

      任何时候脚本创建一个对象,添加一个条目到地图。每当脚本删除对象时,请删除映射条目。当脚本访问对象时,在映射中找到指针,从而确认它存在并且类型正确。

      【讨论】:

        【解决方案6】:

        此有效检查仅在 windows (VS) 中检查,功能如下:

        #pragma once
        //ptrvalid.h
        __inline bool isValid(void* ptr) {
            if (((uint)ptr)&7==7)
                return false; //Not valid address at all (Maybe random pointer?)
            char _prefix;
            __try {
                _prefix=*(((char*)ptr)-1); //Get the prefix of this data
            } __except (true) { //Catch all unique exceptions (Windows exceptions) 
                return false; //Can't reach this memory
            }
            switch (_prefix) {
            case 0:    //Running release mode with debugger
            case -128: //Running release mode without debugger
            case -2:   //Running debug mode with debugger
            case -35:  //Running debug mode without debugger
                return false; //Deleted :(
                break;
            }
            return true; //Still alive!
        }
        

        用法:

        #include <stdio.h>
        #include "ptrvalid.h"
        
        void PrintValid(void* ptr) {
            if (isValid(ptr))
                printf("%d is valid.\n",ptr);
            else
                printf("%d is not valid.\n",ptr);
        }
        
        int main() {
            int* my_array=(int*)malloc(4);
            PrintValid(my_array);
            PrintValid((void*)99);
            free(my_array);
            PrintValid(my_array);
            my_array=new int[4];
            PrintValid(my_array);
            delete my_array;
            PrintValid(my_array);
            getchar();
        }
        

        输出:

        764776 is valid.
        99 is not valid.
        764776 is not valid.
        774648 is valid.
        774648 is not valid.
        

        函数的解释:(它的作用)

        函数在真正检查之前检查,如果地址有效\起点指向内存。 之后,他检查这个进程是否可以到达这个内存的前缀(如果不能捕获异常),最后一个检查是检查这个内存的前缀是否在任何模式下被删除。 (调试\无调试模式\发布模式) 如果函数通过了所有这些检查,则返回 true。

        【讨论】:

        • 您重新发明了IsBadXxxPtr 函数,但不够健壮。请参阅here,了解为什么这可能是一个坏主意的合理解释。以前的回答,早在这个回答之前就发布了,已经引用了这个相同的链接。我怀疑人们出于这个原因对此表示反对,再加上他们想要阻止其他人使用/考虑这是一个非常糟糕的主意。
        • @CodyGray 生成随机指针时抛出的异常。当您刚刚写(void*)52256 时,它是有效地址。但是如果你的地址真的被分配了,它不会抛出任何异常来返回 false。
        • 很好的解决方案,很高兴知道一个接受地址并检查地址是否绝对无效(并且会导致取消引用崩溃)或可能无效(但不会崩溃)的函数.对于允许用户访问他们想要的任何内存的解释上下文(例如解释 c),了解解释上下文是一件好事。它将允许程序提供比“去调试程序”更有意义的信息,例如“函数 x 试图解决非法内存,您可能想要调试它。”。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-08-20
        • 1970-01-01
        • 2011-03-05
        • 2022-08-22
        • 2011-06-07
        • 2020-03-29
        相关资源
        最近更新 更多