【问题标题】:Detect modification of variable at runtime in C/C++在 C/C++ 中检测运行时变量的修改
【发布时间】:2010-03-09 11:10:59
【问题描述】:

我正在用 C++ 开发一个库,用户/程序员将在其中扩展一个类 BaseClass,该类有一个方法 initArray。此方法应由用户/程序员实现,通常应初始化数组m_arr 的所有元素。

这是一个片段,修改为这个例子:

class BaseClass {
     public:

     BaseClass(int n) {
         m_arr = new double[n]; 
         size = n;
     };
     virtual ~BaseClass();

     int size;
     double* m_arr;

     virtual int initArray();
};

有时,用户/程序员实现了一个initArray,它不会初始化 m_arr 的某些元素。我想要的是在我的库中创建一个函数来检查initArray 是否初始化了m_arr 的所有元素。此函数应在运行时由健全性检查程序调用。

我的问题:是否可以检测到此阵列上的更改?我只能想到用一些无效值(如 NaN 或 Inf)初始化数组,调用 initArray 并检查所有值是否已更改。

感谢您的想法,

大卫


编辑

这是我尝试检测的客户端代码示例:

// .h:
class MyExample : public BaseClass {
     public:
     MyExample();
     virtual ~MyExample();
     virtual int initArray();
};

// .cpp:
MyExample::MyExample() : BaseClass(3) { }
MyExample::~MyExample() { }
int MyExample::initArray() {
     m_arr[0] = 10;
     //m_arr[1] = 11; // let's say someone forgot this line
     m_arr[2] = 12;
     return 0;
}

因此,如果忘记m_arr[1],此元素不会被初始化,可能会导致未来计算出现问题。这就是我想检查的内容。

【问题讨论】:

  • 当然,将 m_arr 设为私有并提供访问器。

标签: c++ c memory


【解决方案1】:

为什么不使用 std::vector?然后最终用户将使用 push_back 添加到它,您可以检查大小以查看添加了多少项目。

【讨论】:

  • 因为我正在维护这段代码,它大量使用数组结构将库与一些用 C 编写的库耦合。
  • @Neil Butterworth 请注意,std::vector 在某些大小更改时会重新分配,并且元素的内存位置也可能会更改。这有时很关键。
  • @Neil Butterworth:是的,我同意,而且可能 std::vector 是未来版本的方式。我可以更改代码以使用向量,并仔细检查与使用 double* 的 C 库的耦合。然而问题是已经有许多用户/程序员以这种方式编写 initArray,作为一个 double*,我希望对用户/程序员的影响尽可能小。
  • 在您现有的代码中,您预先知道大小:为什么不保留那么多空间,并使用向量的想法?编辑:不能拼写。
  • @Mark B 因为你找不到数组是否已初始化。
【解决方案2】:

我没有看到在 C++ 中执行此操作的直接方法。您打算在 Ruby on Rails 中实现过滤器,在访问任何方法之前,都会调用过滤器。

或者,您可以将数组包装在一个结构中,并在该结构中重载 [] 运算符以进行赋值和访问。

现在:

1) 在重载的 [] 运算符中进行赋值,维护一个计数器,每次初始化时递增计数器。

2) 在重载的 [] 访问运算符中,在访问数组内容之前检查计数器是否等于元素总数。如果不是,则抛出错误。

希望这会有所帮助。

【讨论】:

  • 我喜欢这个答案,因为它与示例提出的结构保持一致。
  • m_arr[0] = 10; m_arr[0] = 11; // 糟糕,复制粘贴错误! m_arr[2] = 12;
  • @Mark 感谢您指出这一点。您可以在递增计数器之前添加条件以检查当前元素是否尚未初始化
  • 这比这要困难得多:m_arr[0] = 10; if (m_c) m_arr[0] = 11; 会弄乱计数器...之后检查数组要容易得多,而且每次调用 m_arr[] 时也不会产生开销,因为一旦你过了初始化阶段,它就没什么用了......
【解决方案3】:

如果您想制作一个“万无一失”的界面,而不是将initArray() 视为具有副作用的例程...为什么不将其风格化为更实用的东西呢? initArray() 可以负责分配和构造一个向量,并传回对该完全构造对象的引用。这也可以让您将m_arr 设为私有:

class BaseClass {
private:
    size_t size;
    auto_ptr< vector< double > > m_arr;

public:
    BaseClass(size_t n) {
        size = n;
     };
     virtual ~BaseClass();

protected:
     virtual auto_ptr< vector< double > > initArray() const;
};

(注意:在调用 initArray 之后,您可以验证派生类传回的向量的大小是否正确。或者,如果您不需要预先强制执行大小,您可以直接从构造函数并接受 initArray 返回的任何长度作为预期大小。)

【讨论】:

  • 我同意你的功能方法。我想我会走这条路,因为我喜欢没有副作用的常规。但是,正如我告诉 Neil 的那样,我确实有很多用户/程序员已经使用 initArray 及其副作用进行了编码,所以我希望改进这个库的一些细节,同时对他们的习惯影响很小。
【解决方案4】:

这是我的建议:

在基类中添加另一个受保护的虚拟方法,称为“InitArrayImpl”,并命令子类的创建者将数组填充到其中。

initArray方法应该是public non-virtul,在这个方法中调用InitArrayImpl,调用前用无效值初始化数组,调用后测试是否所有值都改变了。

类的客户端应该只暴露给 initArray 方法。

【讨论】:

  • void Base::initArray() { initArrayWithInvalidValue(); initArrayImpl();检查数组();其中 initArrayImpl() 是一个虚函数。
  • 是的,我最初的想法是朝这个方向发展。这就是我问你们的原因,因为它可能可以通过其他方式完成。
  • 这是惯用的方式。确实,如果您听听 Herb Sutter 的建议(而且好像他什么都知道?),接口不应该暴露虚方法。调用私有虚拟方法的非虚拟方法允许检查基类中的前置条件和后置条件。
【解决方案5】:

如果效率不是您的主要目标之一,您可以

  • 通过将m_arr设为私有来阻止直接访问
  • 添加与 m_arr m_field_initialized = new bool[n] 大小相同的 bool 数组,并使用 false 值对其进行初始化。
  • 创建将包装m_arrm_field_initialized 的访问器类的公共(或受保护)实例
  • 访问器应该有setVal(int i, double val) 方法,它将设置m_arr[i] 和另外的m_field_initialized[i] 标志为true;
  • 在您的初始化检查方法中测试m_field_initialized 的所有字段是否都设置为true。

当满足初始化条件时,您可以通过提供更快的直接访问方法来改进这一点。初始化检查前返回空指针,初始化成功后返回数组指针。

double * directAccess() {
   if (m_arr_initialized) return m_arr;
   else return 0;
}

m_arr_initialized由你的初始化检查方法设置。


如果不需要在基类中分配数组,您可以将m_arr 设置为零,将分配留给子类并检查m_arr 指针是否设置为非零。可以设置表示分配大小的附加有效字段。或者您可以像以前一样阻止对 m_arr 的访问,并在基类中提供分配方法allocate(std::size_t size),或使用初始值分配allocate(std::size_t size, double initVal),甚至强制传递初始化函数allocate(std::size_t size, double (*callback)(std::size_t element)),它将在每个元素上调用。有很多可能性。


编辑:在您编辑后,我建议使用指向初始化对象或回调到 BaseClass 构造函数中的函数的指针(或引用)。这将强制子类提供初始化代码。考虑以下几点:

class InitializerInterface
{
   public:
     virtual double get(int element) const = 0; 
}

在你的基类构造函数中

 BaseClass(int n, const InitializerInterface & initializer) {
     m_arr = new double[n]; 
     size = n;
     for (int i = 0; i < n; i++)
        m_arr[i] = initializer.get(i);
 };

现在任何子类都必须将一些初始化传递给构造函数。您当然可以用仿函数替换 get() 方法或添加对回调函数的支持。这取决于您的喜好。

//最后一次编辑以使其 const 正确

【讨论】:

    【解决方案6】:

    这看起来不太像 C++,而更像 C,并带有一些有用的特性,比如对我来说是构造函数。您的类无法确定它是 C 结构还是 C++ 类,我认为这是您在此处初始化时遇到的问题的一部分。

    如果以不同的方式来做这件事,将数组设为私有并提供一个非虚拟初始化接口,该接口接受类似于 std::generate 的仿函数。然后为每个元素调用一次仿函数。这样您就知道他们是否调用了您的初始化程序,并且您知道所有元素都已安全初始化。不仅如此,它们还可以防止子类随时更改它们。

    如果您出于某种原因需要保留当前方法,如果您可以保证这些值不会在您计划为其发布库的任何硬件上捕获异常,则使用 NaN 或 inf 可能会起作用。更安全的是,只需选择一些中性值,例如 0 或 1,如果客户端无法初始化,只需清楚地表明他们是在踢自己的脚。

    换句话说,让这些数据公开并强制它被合理地初始化是两个(几乎)相互排斥的目标。

    【讨论】:

    • 它看起来像 C + 几个特性,因为代码被大量修剪。我同意你的 NaN 观点。
    【解决方案7】:

    您还可以通过调试寄存器设置数据断点。 这个link 有一些关于这个话题的信息。

    【讨论】:

    • 谢谢,但我正在寻找运行时解决方案,没有调试器。
    【解决方案8】:

    您使用 inf 或 NaN 的想法会奏效。任何预先确定的初始化程序都可以很好地工作。

    另一个想法是创建一个访问器成员函数来修改元素。在访问器中设置修改后的标志,在 InitArray() 中清除修改后的标志。

    【讨论】:

    • 具有 NaNs 的想法将一直有效,直到子类本身不使用 NaNs 初始化数组。不幸的是,这可能是子类功能的一部分。试想一下,子类有两个参数a, b,初始值为a/b。结果 NaN(例如在 a=0 和 b=0 的情况下)是其规范的一部分。或者,另一方面,初始化可能非常复杂,以至于由于轮错误,它可能会无意中返回 NaN。这种方法可能发生的错误可能很难跟踪。
    【解决方案9】:

    这取决于你想要做什么:你真的需要在基类中创建数组,还是可以使用尼尔的push_back() 的建议?

    无论如何,考虑使用std::vector&lt;&gt;(为什么要自己管理内存?)boost::optional&lt;double&gt;。 (你知道boost libraries吗?)

    另外,您真的要将对象创建分为两个阶段吗?任何创建BaseClass 后代的客户端代码都需要调用initArray()。你关心效率吗?

    【讨论】:

    • 这种设计是因为 BaseClass 实际上是一个有状态的对象(就像在模拟中一样)。 initArray 初始化对象的状态,随着状态的变化,它可以被初始化多次。
    • boost::optional 很棒,但不能安全地暴露在 C 代码中。
    【解决方案10】:

    我认为你在这件事上吹口哨。您不能规定所有值都正确填写。您最多只能规定用户将一些值填充到数组元素中。如果您将 NaN 填充到数组中,然后进行完整性检查,您的所有用户将做的就是用零、-1 或 MAX_DOUBLE 进行初始化。所以你不妨为他们提供一个演员来做这件事并完成它。

    我的派生类可以很好地使用基类来保留 1000 个元素,但它可能包含它自己对实际使用了多少元素的计数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多