潜在的动态内存问题

Potential dynamic memory problems

本文关键字:内存 问题 动态      更新时间:2023-10-16

我正在使用C 创建Arduino设备。我需要一个具有可变大小和可变数据类型的堆栈对象。从本质上讲,此堆栈需要能够调整和与字节,字符,ints,Doubles,floats,Shorts和longs一起调整和使用。

我有一个基本的类设置,但是有了需要大量的动态内存分配,我想确保我使用数据的使用足够足够的空间以使程序在没有内存问题的情况下继续进行。这不使用性标准方法,而是在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)未能致电deinit(),当此类的任何实例不在范围并被破坏时,将泄漏内存。每次使用此类课程时,您都必须认识到此类实例何时范围范围并被摧毁。每次创建此类实例时,都很容易跟踪,并且每次都可以轻松记住init()。但是,跟踪此类实例的各种可能方式可能会超出范围并被销毁,以便您必须调用deinit()并释放内部内存,这要困难得多。甚至没有意识到何时发生很容易。

2)如果此类的实例被复制构造,或者调用了默认分配运算符,则可以保证这会导致内存损坏,并且内存泄漏的额外侧面保存。

请注意,您不必竭尽全力编写复制构造的代码,也不必将对象的一个实例分配给另一个实例。如果您不关注,编译器将很乐意为您做。

通常,避免此类问题的最佳方法是通过正确使用该语言使其不可能实现。即:

1)遵循RAII设计模式。摆脱init()deinit()。相反,在对象的构造函数和破坏器中进行此工作。

2)要么删除复制构造函数和分配运算符,要么正确实现。因此,如果绝不应该对该类的实例进行复制构造或分配,那么最好让编译器对您大喊大叫,如果您不小心编写了一些执行此操作的代码,而不是花一周的时间跟踪发生的地方。或者,如果可以对课程进行复制构造或分配,请正确执行。

当然,如果只有此类的少数实例,则应该可以安全地使用它,紧密控制和大量护理,而无需进行这种重新设计。但是,即使是这种情况,也总是最好做好工作,而不是立即耸耸肩,而是后来决定扩大该课程的使用,然后忘记了该课程是因此容易出错。

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

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

这是不对的。data_arraybyte *,因此sizeof(data_array)将始终是一个编译时常数,即sizeof(byte *)。这显然不是您想要的。您需要明确跟踪分配的阵列的大小。

相同的一般错误出现在其他几个地方,但很容易修复。整个班级设计是更大的问题。