如何确保函数不会获得垃圾指针?

how to make sure that a function won't get a garbage pointer?

我有一个接受指针的函数和 return 一个 enum 取决于与该指针相关的一些条件:

my_enum function(char* prt)
{
  /* function body*/
  if (condition1) return enum1;
  if (condition2) return enum2;
  if (condition3) return enum3;
  if (condition4) return enum4;
  else return enum5;
}

我有另一个函数,它也接受一个指针,调用 my_function 并对获得的值做出反应:

void another_function(char* ptr)
{
 my_enum result = function(ptr);
 if (result == MY_VALUE) std::cout<<"OK"<<endl;
}

我是 运行 Valgrind 来检查内存泄漏。上面的代码导致以下错误:

Conditional jump depends on an uninitialized variable.

实际上,可以将未初始化的指针传递给函数function

我的问题是:处理这种情况的最佳方法是什么(除了使用引用之外)?我不能确定每个使用该代码的人都会初始化他将传递给该函数的指针。如果指针指向一些垃圾(我正在检查它是否是空指针),我也无法检查我的函数内部。

我应该忽略此类错误吗?如果它们没用,为什么 Valgrind 还要告诉我它们呢?一定有我能做的。

基本上有两种解决方案。

  1. 期待一个有效的指针,并在您的 API 的文档中明确说明。那么任何无效的使用都会导致UB,但这不是你的错。但是,处理原始指针是 C 风格的,C++ 程序员不赞成。

  2. 采用(引用)一个封装的指针类型,它总是被合理地初始化,例如std::string(而不是const char*),std::unique_ptr,或 std::shared_ptr。例如,

    my_enum function(std::string const&str)
    {
      /* function body*/
      if (str.empty()) // deal with improper input
        std::cerr<<"warning: empty string in function()"<<std::endl;
      if (condition1) return enum1;
      if (condition2) return enum2;
      if (condition3) return enum3;
      if (condition4) return enum4;
      else return enum5;
    }
    

    my_enum function(std::unique_ptr<SomeType> const&ptr)
    {
      /* function body*/
      if (!ptr) { // deal with improper input
        std::cerr<<"warning: invalid pointer in function()"<<std::endl;
        return enum_error;
      }
      if (condition1) return enum1;
      if (condition2) return enum2;
      if (condition3) return enum3;
      if (condition4) return enum4;
      else return enum5;
    }
    

    这避免了原始指针,是处理这种情况的 C++ 方法。后一种代码的一个问题是它仅适用于 unique_ptr 个参数。人们可以将其概括为重载(使用 SFINAE 或其他方式)以获取(常量引用)任何自动指针,如对象(例如定义为对象 obj,成员 obj::get() const 返回 const obj::element_type*).

你愿意走多远?如果有人想破解你的代码,他们会的,你没办法。

您应用的保护越有效,他们就越难。

一个简单的方法是检查 NULL。这并不能防止愚蠢的指针,但它可以防止有意识地无效。大多数人对此感到满意。

然后你可以给指针一个包装器class。实例化这个 class 需要指向一个有效的对象(或者一些无望的跳跃通过箍给它一个无效的,这相当于故意射击你的脚),所以不会发生未初始化指针的情况 - 但对象可以停止在使用其指针之前存在。

然后你可以为这些对象和它们的指针维护一个factory/manager class。每次创建或销毁指针目标对象时,都会创建或使其指针失效。这将是防故障的,除非您的代码是多线程的,并且在您的函数已经通过检查并且在使用验证值之前可能会发生破坏。

然后您可以添加线程安全,将您的函数和管理器包装在互斥体中。这增加了与死锁和同步相关的各种麻烦。但是用户必须非常努力地创建一个从你的派生而来的 class(可能首先使用 #define private public)来覆盖其安全功能......

每走一步,您的头顶都会攀升到效果真正不再值得付出努力的水平。因此,只需检查该指针是否为 NULL,不要再担心其他人会把你弄出来。

关于什么是 "best" 方法的观点会有所不同,因为根本不可能阻止某人传递错误的(例如未初始化的、悬空的)指针。

一个常见的解决方案是完全避免使用原始指针,并以完全不接受指针的方式编写函数。

一种方法是接受引用。编写代码使其完全不使用原始指针会使调用带有错误参数的函数变得更加困难。限制是调用者仍然可以创建一个错误的引用(例如,通过取消引用一个错误的指针)但是它需要更多的努力(或者如果无意中犯了错误序列更长的错误)将错误的引用传递给一个函数而不是传递一个错误的引用指针错误。

另一种方法是按值(或引用,在某些情况下)接受一些 class 对象来保存您的指针。然后实现 all 的成员函数,以防止持有错误指针的情况。给出 class 没有接受指针的成员函数。确保构造函数和其他成员函数保持一致性(形式上,构造函数建立一组严格的不变量,其他成员函数维护那组不变量)。这包括在尝试使用错误数据构造对象时抛出异常的技术(如果在构造对象的过程中抛出异常,则该对象永远不存在,并且不能以任何方式传递给您的函数)。因此,您的函数可以假设 - 如果它被成功调用 - 它接收到的数据是有效的。

事实是,以上内容使得错误数据更难意外传递给您的函数。没有任何技术可以绝对阻止有足够决心(无论是天才还是愚蠢)的人找到绕过所有安全措施并将错误数据传递给您的函数的方法。