为堆中的对象保留内存而不在 Arduino 上调用其构造函数

Posted

技术标签:

【中文标题】为堆中的对象保留内存而不在 Arduino 上调用其构造函数【英文标题】:Reserving memory for an object in heap without calling its constructor on Arduino 【发布时间】:2022-01-08 07:34:33 【问题描述】:

我正在开发一个 arduino 项目,这是相关的,因为 arduino 本身不支持 STL,也不支持动态分配。我注意到我正在编写的很多类在构造上没有任何作用,但是有一个 .init() 方法可以实际初始化任何资源。这是因为这样可以在全局范围内初始化类,然后当 setup 函数运行时,实际的初始化发生在调用 .init() 时。

例如:

const portn_t en=A5, rs=A4, d4=A0, d5=A1, d6=A2, d7=A3;
// making room for that object in the global scope
// this way it can be both used in ``setup()`` and ``loop()``
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() 
  lcd.begin(16, 2);  // Doing the actual initialization


void loop() 
  lcd.clear();
  lcd.write("Hello world!");
  delay(500);

这适用于使用initbegin 方法设计的类。这种设计模式在大多数 Arduino 库中很常见,但对于没有实现它的类,我目前正在使用它作为解决方法:

Button& get_btn_up() 
  // The Button class actually does initialization at construcion
  static Button bt(2, true);
  return bt;


Button& get_btn_enter() 
  static Button bt(3, true);
  return bt;


Button& get_btn_down() 
  static Button bt(4, true);
  return bt;


void setup() 
  // Initializes all the ``Button`` objects
  get_btn_up();
  get_btn_enter();
  get_btn_down();


void loop() 
  auto up = get_btn_up();
  if (up.just_pressed()) 
    ...
  
  ...

我不认为这是一个最佳解决方案,因为有很多样板只是为了实现可以用new 和一些独特的指针来完成的事情。 因此,我尝试创建一个DelayedInit 容器类,它将在联合中保存对象所需的内存并处理其生命周期

template<typename T>
union MemoryOf 
  uint8_t memory [sizeof(T)];
  T obj;
;

template<typename T>
struct DelayedInit 
private:
  MemoryOf<T> memory.memory= 0 ;
  bool is_set = false;
public:
  T& get() const 
    memory.obj;
  
  DelayedInit() 
  ~DelayedInit() 
    if (is_set)
      get().~T();
  
  T* operator->() const 
    return &get();
  
  T& operator*() 
    is_set = true;
    return get();
  
  const T& operator*() const 
    return get();
  
  explicit operator bool() const 
    return is_set;
  
;

这个实现当时被破坏了,因为每当我尝试调用盒装类的任何方法时,它都会锁定 arduino。以下是它应该如何使用

DelayedInit<Button> up, enter, down;

void setup() 
  *up = Button(2, true);
  *enter= Button(3, true);
  *down = Button(4, true);


void loop() 
  if (up->just_pressed())   // Locks up the arduino
    ...
  
  ...

我猜代码中存在一些我不知道的内存管理错误。 DelayedInit 实现中存在哪些错误?有没有更好的方法来解决这个问题?

【问题讨论】:

展示位置new()? 您之前忘记了return(它本身完全没有任何作用)memory.obj;,但这是最少的问题。模板在需要时忘记构造对象。这是未定义的行为。将某些东西分配给未构造的对象是更加未定义的行为。您基本上需要从 C++17 重新实现 std::variant。这是很多代码,要正确执行此操作,比这里显示的要多得多。 @SamVarshavchik 感谢您提及退货问题! arduino sdk 中包含的编译器仅支持没有 STL 的 c++11,std::vairantstd::optional 不是一个选项。 @EOF Placement new 现在我考虑一下肯定是一个选择。您介意详细说明如何将其集成(或不集成)到DelayedInit 类中吗? @Facundo 您可以在已分配的内存上调用placement new,然后您可以使用常规构造函数而不是进行荒谬的扭曲以避免编写理智的构造函数。 【参考方案1】:

这是我最终得到的解决方案。 .init 模拟了 arduino 库中大多数类使用的模式,而实际上并未将 init 逻辑拆分为两种方法。肯定有改进的余地,但这适用于全局变量。

#pragma once
#include <new>
template<typename T>
union MemoryOf 
  uint8_t memory [sizeof(T)];
  T obj;
  MemoryOf() : memory 0  ;
  ~MemoryOf() ;
;

// Only tested with global variables
// so there might be bugs in the cleanup bits
template<typename T>
struct DelayedInit 
private:
  MemoryOf<T> memory;
  bool is_set = false;
public:
  const T& get() const 
    return memory.obj;
  
  T& get() 
    return memory.obj;
  
  void remove() 
    get().~T();
  
  DelayedInit() ;
  ~DelayedInit() 
    if (is_set)
      remove();
  
  template<typename ...Args>
  inline void init(Args ...args) 
    if (is_set) 
      remove();
    
    new (&memory.memory) T(args...); // == &memory.obj
    is_set = true;
  
  T* operator->() 
    return &get();
  
  const T* operator->() const 
    return &get();
  
  T& operator*() 
    return get();
  
  const T& operator*() const 
    return get();
  
  explicit operator bool() const 
    return is_set;
  
;

用法:

#include "delayed_init.hpp"

DelayedInit<Button> up, enter, down;

void setup() 
  up.init(2, true);
  enter.init(3, true);
  down.init(4, true);


void loop() 
  if (up->just_pressed()) 
    ...
  
  ...

感谢EOF 建议使用放置new

【讨论】:

以上是关于为堆中的对象保留内存而不在 Arduino 上调用其构造函数的主要内容,如果未能解决你的问题,请参考以下文章

内存泄露与内存溢出

堆中的 DLL 内存泄漏

成员变量和局部变量

成员变量与局部变量

java学习第七天

如何在 Java 中查看堆中的内容?