wakisuke
@wakisuke

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

arduino(Blynk)のオブジェクトポインタの考え方について

arduino(Blynk)のオブジェクトポインタの考え方について

やりたかったこと

BlynkTimeinputのウィジェットを使ってStart,Stopの時間を入力して、

  • 入力されたStart時間以降に何かしらの処理を定期的に実行
  • 入力されたend時間以降に定期的に実行していた処理を停止

ということをさせたかったです。

つまずいた点

BlynkTimeinputのウィジェットを使用するにはTimeInputParamを使うのですが、コンストラクタの指定が必須となっており、グローバル変数として定義できません。
下のコードのBLYNK_WRITE{}paramTimeInputParamのコンストラクタに渡す必要があります。

しかし、やりたいのは指定時間で処理の開始(定期実行)・指定時間で処理の停止を行いたいので、
BLYNK_WRITE{}の外(定期実行のBlink.timer 下記コードのvoid t())で指定時間で処理の開始・停止を記述する必要がありました。

やってみたこと

コンストラクタ必須のオブジェクトをポインタとして宣言し、
特定の条件下で実行される関数(下記コードでBLYNK_WRITE(V1) {})でオブジェクトを作成し、ポインタがその関数内で作られたオブジェクトを指すように記述した後、オブジェクトの破棄をせず
別の関数からポインタへアクセスしてオブジェクトを取得する方法で目的の動作はしました。

しかし、これはCのオブジェクトが明示的に開放するまで保持される仕様を悪用したものだと思われます。
試してはいませんが、スマホ上でTimeinputを何回も設定すると(BLYNK_WRITE(V1) {}が設定の度に実行される)メモリリークを起こしそうな気がします。

解決方法として思いつくのはTimeInputParamのプロパティ

  • isWeekdaySelected(1)
  • isWeekdaySelected(2)
  • isWeekdaySelected(3)
  • isWeekdaySelected(4)
  • isWeekdaySelected(5)
  • isWeekdaySelected(6)
  • isWeekdaySelected(7)
  • getStartHour
  • getStartMinute
  • getStopHour
  • getStopMinute

をグローバル変数でboolやintを宣言しておき、BLYNK_WRITE(V1) {}実行時にそこに代入すればいいのだと思われます。
ただ個人的にはもっとスマートな書き方があるように思えます。

できるのかわかりませんが、例えば、オブジェクトをディープコピーする方法や、ポインタへの再定義時に以前のオブジェクトを開放する方法など。

質問

ポインタを更新する(コード上??許される??)記述はメモリリークを起こすのか?ということと、
もしもっとこうしたらいいという考え方があれば教えていただきたいと思います。

よろしくお願いいたします!!!

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#define JST     3600* 9


#define BLYNK_PRINT Serial
#include <BlynkSimpleEsp8266.h>

#include <Ticker.h>


// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "";
char pass[] = "";

static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
TimeInputParam *StartStopTimeObj = NULL; // <---- ポインタ宣言

BlynkTimer timer; // Announcing the timer

WiFiUDP ntpUDP;

// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
// NTPClient timeClient(ntpUDP, "192.168.1.1", JST, 60000);
NTPClient timeClient(ntpUDP, "ntp.nict.jp", JST, 60000);

BLYNK_CONNECTED(){
  Blynk.syncAll();
}

BLYNK_WRITE(V1) {
  // TimeInputParam t(param);
  StartStopTimeObj = new TimeInputParam(param); // <---- ポインタがヒープ領域へ作られたオブジェクトを指すように指定 ??許される??
}

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.print("\n\nStart\n");

  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.println();
  Serial.printf("Connected, IP address: ");
  Serial.println(WiFi.localIP());


  Blynk.begin(auth, ssid, pass, IPAddress(192, 168, 1, 26), 8080);

  timer.setInterval(1000L, t); //timer will run every sec
}

void loop() {
  timeClient.update();

  Blynk.run();
  timer.run();
}

void t()
{
  char s[] = "{W} HH:MM:SS";

  sprintf(s, "%s %02d:%02d:%02d", wd[timeClient.getDay()], timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds());
  Serial.println(s);
  // Serial.println(timeClient.getFormattedTime());


    // Process start time
  char ss[] = "Input {Sun,Mon,Tue,Wed,Thr,Fri,Sat} HH:MM - HH:MM";

  // Process weekdays (1. Mon, 2. Tue, 3. Wed, ...) // <---- ↓廃棄していないためたまたま残っている?ヒープ領域へ作られたオブジェクトへアクセス
  sprintf(ss, "Input {%s,%s,%s,%s,%s,%s,%s} %02d:%02d -> %02d:%02d",
          StartStopTimeObj->isWeekdaySelected(1) ? wd[1] : "---",
          StartStopTimeObj->isWeekdaySelected(2) ? wd[2] : "---",
          StartStopTimeObj->isWeekdaySelected(3) ? wd[3] : "---",
          StartStopTimeObj->isWeekdaySelected(4) ? wd[4] : "---",
          StartStopTimeObj->isWeekdaySelected(5) ? wd[5] : "---",
          StartStopTimeObj->isWeekdaySelected(6) ? wd[6] : "---",
          StartStopTimeObj->isWeekdaySelected(7) ? wd[0] : "---",
          StartStopTimeObj->hasStartTime() ? StartStopTimeObj->getStartHour() : 99,
          StartStopTimeObj->hasStartTime() ? StartStopTimeObj->getStartMinute() : 99,
          StartStopTimeObj->hasStopTime() ? StartStopTimeObj->getStopHour() : 99,
          StartStopTimeObj->hasStopTime() ? StartStopTimeObj->getStopMinute() : 99);
  Serial.println(ss);
// <---- ↑廃棄していないためたまたま残っている?ヒープ領域へ作られたオブジェクトへアクセス

  Serial.println();

  delay(1000);
}
0

1Answer

結果解決しました

色々勘違いや思い込みをしていたので、下記にそれらをメモります。

結果:メモリリークしないようにDeleteすればいい


TimeInputParam *StartStopTimeObj = NULL;

BLYNK_WRITE(V1) {
  delete StartStopTimeObj;
  StartStopTimeObj = new TimeInputParam(param);
}

盛大な勘違い

ArduinoはC/C++
見た目異なるのはArduino側でC/C++への変換をしているらしい

メモリリークの注意点

初回のBLYNK_WRITE実行時に二重開放を注意すること(上記では起きない)
ポインタ宣言時に何も代入しない場合、BLYNK_WRITE実行時にStartStopTimeObjのメモリ解放が実行されるが、その際に開放されるメモリは他で使われている可能性のあるものが削除される。


TimeInputParam *StartStopTimeObj; // 危険

BLYNK_WRITE(V1) {
  delete StartStopTimeObj;
  StartStopTimeObj = new TimeInputParam(param);
}

必ずポインタを宣言する場合はNullで初期化すること。
また、Nullポインタに対してDeleteを行うことは許可されており、何も起きないことが保証されているとのこと。

そのため、初期値にNullをしている場合は、NullチェックせずそのままDeleteして問題ない。
次回からはStartStopTimeObjのインスタンスが入るため、このメモリ解放がDeleteが行われる。

ヒープメモリのチェック

これらを確認するため、下記の通りヒープサイズをチェックするコードを追記した。


#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#define JST     3600* 9


#define BLYNK_PRINT Serial
#include <BlynkSimpleEsp8266.h>

#include <Ticker.h>

#include "Dummy.h"

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "";

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "";
char pass[] = "";

static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
TimeInputParam *StartStopTimeObj = NULL;




// initial stack
char *stack_start;
Dummy *dummyObj1 = NULL;
Dummy *dummyObj2 = NULL;
Dummy *dummyObj3 = NULL;
Dummy *dummyObj4 = NULL;
Dummy *dummyObj5 = NULL;
Dummy *dummyObj6 = NULL;
Dummy *dummyObj7 = NULL;


BlynkTimer timer; // Announcing the timer

WiFiUDP ntpUDP;

// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
// NTPClient timeClient(ntpUDP, "192.168.1.1", JST, 60000);
NTPClient timeClient(ntpUDP, "ntp.nict.jp", JST, 60000);

BLYNK_CONNECTED(){
  Blynk.syncAll();
}

BLYNK_WRITE(V1) {
  // TimeInputParam t(param);
  delete StartStopTimeObj;
  StartStopTimeObj = new TimeInputParam(param);

          if (StartStopTimeObj->isWeekdaySelected(1)) {
            dummyObj1 = new Dummy(100);
            delete dummyObj1;
          }
          if (StartStopTimeObj->isWeekdaySelected(2)) {
            dummyObj2 = new Dummy(100);
            delete dummyObj2;
          }
          if (StartStopTimeObj->isWeekdaySelected(3))
            dummyObj3 = new Dummy(100);
          if (StartStopTimeObj->isWeekdaySelected(4))
            dummyObj4 = new Dummy(100);
          if (StartStopTimeObj->isWeekdaySelected(5))
            dummyObj5 = new Dummy(100);
          if (StartStopTimeObj->isWeekdaySelected(6))
            dummyObj6 = new Dummy(100);
          if (StartStopTimeObj->isWeekdaySelected(7))
            dummyObj7 = new Dummy(100);

Serial.println(String("------ Free Heap Size: ") + ESP.getFreeHeap());  // フリーヒープサイズを出力
}

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.print("\n\nStart\n");

  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.println();
  Serial.printf("Connected, IP address: ");
  Serial.println(WiFi.localIP());

  Blynk.begin(auth, ssid, pass, "", );

  timer.setInterval(1000L, t); //timer will run every sec
  LEDFlashTicker.once_ms(0, LEDFlashON);



Serial.println(String("START Free Heap Size: ") + ESP.getFreeHeap());  // フリーヒープサイズを出力
}

void loop() {
  timeClient.update();

  Blynk.run();
  timer.run();
}
void LEDFlashON()
{
  led1.on();
  LEDFlashTicker.once_ms(1000, LEDFlashOFF);
}
void LEDFlashOFF()
{
  led1.off();
  LEDFlashTicker.once_ms(1000, LEDFlashON);
}


void t()
{
  char s[] = "{W} HH:MM:SS";

  sprintf(s, "%s %02d:%02d:%02d", wd[timeClient.getDay()], timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds());
  Serial.println(s);
  // Serial.println(timeClient.getFormattedTime());


    // Process start time
  char ss[] = "Input {Sun,Mon,Tue,Wed,Thr,Fri,Sat} HH:MM - HH:MM";

  // Process weekdays (1. Mon, 2. Tue, 3. Wed, ...)
  sprintf(ss, "Input {%s,%s,%s,%s,%s,%s,%s} %02d:%02d -> %02d:%02d",
          StartStopTimeObj->isWeekdaySelected(1) ? wd[1] : "---",
          StartStopTimeObj->isWeekdaySelected(2) ? wd[2] : "---",
          StartStopTimeObj->isWeekdaySelected(3) ? wd[3] : "---",
          StartStopTimeObj->isWeekdaySelected(4) ? wd[4] : "---",
          StartStopTimeObj->isWeekdaySelected(5) ? wd[5] : "---",
          StartStopTimeObj->isWeekdaySelected(6) ? wd[6] : "---",
          StartStopTimeObj->isWeekdaySelected(7) ? wd[0] : "---",
          StartStopTimeObj->hasStartTime() ? StartStopTimeObj->getStartHour() : 99,
          StartStopTimeObj->hasStartTime() ? StartStopTimeObj->getStartMinute() : 99,
          StartStopTimeObj->hasStopTime() ? StartStopTimeObj->getStopHour() : 99,
          StartStopTimeObj->hasStopTime() ? StartStopTimeObj->getStopMinute() : 99);
  Serial.println(ss);
  
  delay(3000);
}

Dummy.h

class Dummy
{
    const byte* buffer;

    public:
    Dummy(int size):buffer(new byte[size])
    {
      Serial.println ("!NEW");
    }

    ~Dummy()
    {
        delete [] buffer;
        Serial.println ("!Delete");
    }
};

Blynk側で日曜・月曜を選択した場合は、Dummyのインスタンスが削除後に作成されるため、ヒープメモリのサイズは変わらなかった。
しかし、火曜~土曜はインスタンスを作成するが、削除を行わないため、ヒープメモリは減っていく一方だった。

メモリは勝手に解放されないのか

されないらしい。だからメモリリークが起きるんだそうな。
最近はJetbrainも触る機会がめっぽう減って、何をするにもググらないとなにもできなくなってしまった、、、、

0Like

Your answer might help someone💌