潜在的动态记忆问题

时间:2016-06-18 15:18:37

标签: c++ memory-management arduino dynamic-memory-allocation

我正在使用C ++创建一个Arduino设备。我需要一个具有可变大小和可变数据类型的堆栈对象。本质上,这个堆栈需要能够调整大小,并与字节,字符,整数,双精度,浮点数,短路和长整数一起使用。

我有一个基本的类设置,但是由于需要动态内存分配量,我想确保我使用的数据释放足够的空间让程序继续而不会出现内存问题。这不使用std方法,而是内置Arduino的版本。

为了澄清,我的问题是:我的代码中是否存在潜在的内存问题?

注意:这不在Arduino堆栈交换中,因为它需要深入了解C / C ++内存分配,这对所有C和C ++程序员都有用。

以下是代码:

Stack.h

#pragma once

class Stack {
public:
  void init();
  void deinit();

  void push(byte* data, size_t data_size);
  byte* pop(size_t data_size);

  size_t length();

private:
  byte* data_array;
};

Stack.cpp

#include "Arduino.h"
#include "Stack.h"

void Stack::init() {
  // Initialize the Stack as having no size or items
  data_array = (byte*)malloc(0);
}

void Stack::deinit() {
  // free the data so it can be re-used
  free(data_array);
}

// Push an item of variable size onto the Stack (byte, short, double, int, float, long, or char)
void Stack::push(byte* data, size_t data_size) {
  data_array = (byte*)realloc(data_array, sizeof(data_array) + data_size);
  for(size_t i = 0; i < sizeof(data); i++)
    data_array[sizeof(data_array) - sizeof(data) + i] = data[i];
}

// Pop an item of variable size off the Stack (byte, short, double, int, float, long, or char)
byte* Stack::pop(size_t data_size) {
  byte* data;

  if(sizeof(data_array) - data_size >= 0) {
    data = (byte*)(&data_array + sizeof(data_array) - data_size);
    data_array = (byte*)realloc(data_array, sizeof(data_array) - data_size);
  } else {
    data = NULL;
  }

  // Make sure to free(data) when done with the data from pop()!
  return data;
}

// Return the sizeof the Stack
size_t Stack::length() {
  return sizeof(data_array);
}

1 个答案:

答案 0 :(得分:3)

显然有一些小的代码错误,虽然很重要,但很容易解决。以下答案仅适用于本课程的整体设计:

显示的代码没有任何问题。

但只显示了代码。对未显示的任何代码都没有任何意见。

并且,很可能在其他代码中会出现大量问题和内存泄漏,这些代码将尝试使用此类。

以一种泄漏或破坏记忆的方式使用这个类非常非常容易。正确使用这个类会更加困难,而且更容易搞砸。事实上这些功能本身似乎正确地完成了他们的工作并不会有所帮助,如果你所要做的只是在错误的方向上打喷嚏,并最终没有以正确的顺序或顺序使用这些功能。

仅举几例明显的前两个问题:

1)如果此类的任何实例超出范围并被销毁,则无法调用deinit()将泄漏内存。每次使用这个类时,都必须认识到这个类的实例何时超出范围并被销毁。每次创建此类的实例时都很容易跟踪,每次调用init()都很容易记住。但是跟踪每个可能的方式,这个类的实例可能会超出范围并被破坏,因此你必须调用deinit()并释放内部内存,这要困难得多。很容易甚至没有意识到发生的时间。

2)如果此类的实例被复制构造,或者调用默认赋值运算符,则可以确保这会导致内存损坏,并且有额外的帮助导致内存泄漏。

请注意,您不必完全编写复制构造的代码,也不必将对象的一个​​实例分配给另一个实例。如果你不注意的话,编译器会非常乐意为你做这件事。

通常,避免这类问题的最佳方法是通过正确使用语言使其无法发生。即:

1)关注RAII design pattern。摆脱init()deinit()。相反,在对象的构造函数和析构函数中执行此操作。

2)删除复制构造函数和赋值运算符,或者正确实现它们。因此,如果这个类的实例永远不应该被复制构造或赋值,那么让编译器对你大喊大叫要好得多,如果你不小心写了一些代码那么做,而不是花一周的时间追踪那里发生的事情。或者,如果可以复制构造或分配类,则可以正确执行。

当然,如果这个类只有少量的实例,那么应该可以安全地使用它,使用严格的控制,并且需要很多小心,而不需要进行这种重新设计。但是,即使情况确实如此,做正确的工作总是更好,而不是现在解决这个问题,但后来决定在更多的地方扩大这个课程的使用,然后忘记了这个课程的事实。所以容易出错。

P.S。:我在开头提到的一些小错误:

data_array = (byte*)realloc(data_array, sizeof(data_array) + data_size);

这不可能是正确的。 data_arraybyte *,因此sizeof(data_array)始终是编译时常量,sizeof(byte *)。那显然不是你想要的。您需要明确跟踪分配的数组大小。

同样的一般错误出现在这里的其他几个地方,但它很容易修复。整体课堂设计是一个更大的问题。