Arduinoの世界では、malloc()
との使用はfree()
非常にまれです。純粋なAVR Cではるかに頻繁に使用されますが、それでも注意が必要です。
それは、使用には本当に悪い考えであるmalloc()
とfree()
のArduinoと?
Arduinoの世界では、malloc()
との使用はfree()
非常にまれです。純粋なAVR Cではるかに頻繁に使用されますが、それでも注意が必要です。
それは、使用には本当に悪い考えであるmalloc()
とfree()
のArduinoと?
回答:
組み込みシステムの私の一般的なルールmalloc()
は、プログラムの開始時に大きなバッファーのみを一度だけにすることです(例:)setup()
。問題は、メモリの割り当てと割り当て解除時に発生します。長時間の実行セッションでは、空きメモリの合計がリクエストに十分すぎる場合でも、メモリが断片化し、最終的に十分な空き領域がないために割り当てが失敗します。
(歴史的な観点、興味がない場合はスキップ):ローダーの実装に応じて、ランタイム割り当てとコンパイル時割り当て(初期化されたグローバル)の唯一の利点は、hexファイルのサイズです。すべての揮発性メモリを備えた市販のコンピューターで組み込みシステムを構築した場合、プログラムはしばしばネットワークまたは計装コンピューターから組み込みシステムにアップロードされ、アップロード時間が問題になることがありました。画像からゼロでいっぱいのバッファを除外すると、時間を大幅に短縮できます。)
組み込みシステムで動的なメモリ割り当てが必要な場合、一般的にmalloc()
、またはできれば静的に大きなプールを割り当て、それを固定サイズのバッファー(またはそれぞれ小さなバッファーと大きなバッファーのそれぞれ1つのプール)に分割し、独自の割り当て/そのプールからの割り当て解除。次に、固定バッファサイズまでの任意の量のメモリを要求するたびに、それらのバッファのいずれかが使用されます。呼び出し側の関数は、要求よりも大きいかどうかを知る必要はありません。また、ブロックの分割と再結合を回避することにより、断片化を解決します。もちろん、プログラムに割り当て/割り当て解除のバグがある場合は、メモリリークが発生する可能性があります。
通常、Arduinoスケッチを作成するときは、動的割り当て(C ++インスタンスを使用する場合、malloc
またはnew
C ++インスタンスを使用する場合)を避け、グローバルstatic
変数またはグローバル変数、またはローカル(スタック)変数を使用します。
ダイナミックアロケーションを使用すると、いくつかの問題が発生する可能性があります。
malloc
/ free
呼び出し後)私が直面したほとんどの状況では、動的割り当ては不要であるか、次のサンプルコードのようにマクロで回避できます。
MySketch.ino
#define BUFFER_SIZE 32
#include "Dummy.h"
Dummy.h
class Dummy
{
byte buffer[BUFFER_SIZE];
...
};
を使用せずに#define BUFFER_SIZE
、Dummy
クラスのbuffer
サイズを固定しない場合は、次のように動的割り当てを使用する必要があります。
class Dummy
{
const byte* buffer;
public:
Dummy(int size):buffer(new byte[size])
{
}
~Dummy()
{
delete [] bufer;
}
};
この場合、最初のサンプルよりも多くのオプションがあります(たとえば、それぞれにDummy
異なるbuffer
サイズの異なるオブジェクトを使用します)が、ヒープの断片化の問題がある可能性があります。
インスタンスが削除buffer
されると、動的に割り当てられたメモリが確実に解放されるように、デストラクタを使用することに注意してくださいDummy
。
で使用されているアルゴリズムmalloc()
をavr-libcから調べましたが、ヒープの断片化の観点から安全な使用パターンがいくつかあるようです。
つまり、プログラムの最初に必要なものをすべて割り当て、決して解放しないでください。もちろん、この場合、静的バッファも使用できます...
意味:他のものを割り当てる前にバッファを解放します。合理的な例は次のようになります。
void foo()
{
size_t size = figure_out_needs();
char * buffer = malloc(size);
if (!buffer) fail();
do_whatever_with(buffer);
free(buffer);
}
内部do_whatever_with()
にmallocがない場合、またはその関数が割り当てたものをすべて解放する場合、断片化から安全です。
これは、以前の2つのケースの一般化です。ヒープをスタックのように使用する場合(後入れ先出し)は、フラグメントではなくスタックのように動作します。この場合、最後に割り当てられたバッファのサイズを変更しても安全であることに注意してくださいrealloc()
。
これは断片化を防ぐことはできませんが、ヒープが最大使用サイズより大きくならないという意味で安全です。すべてのバッファのサイズが同じである場合、バッファの1つを解放するたびに、後続の割り当てにスロットが使用できることを確認できます。
(malloc
/ free
またはnew
/ を介して)動的割り当てを使用することdelete
は、本質的に悪いことではありません。実際、文字列処理のようなもの(たとえば、String
オブジェクトを介した)の場合、非常に役立ちます。これは、多くのスケッチが文字列のいくつかの小さな断片を使用しているためであり、それらは最終的には大きな断片に結合されます。動的割り当てを使用すると、それぞれに必要なだけのメモリを使用できます。対照的に、それぞれに固定サイズの静的バッファーを使用すると、コンテキストに完全に依存しますが、多くのスペースを浪費することになります(メモリーをはるかに速く使い果たすことになります)。
これらすべてが述べられているので、メモリ使用量が予測可能であることを確認することは非常に重要です。実行時の状況(入力など)に応じてスケッチが任意の量のメモリを使用できるようにすると、遅かれ早かれ問題が発生しやすくなります。場合によっては、完全に安全な場合があります。たとえば、使用量が多くならないことがわかっている場合です。ただし、プログラミングプロセス中にスケッチが変更される場合があります。何かが後で変更されたときに、早期に行われた仮定が忘れられ、予期しない問題が発生する可能性があります。
堅牢性を確保するため、通常は可能な限り固定サイズのバッファーを使用し、最初からこれらの制限を明示的に使用するようにスケッチを設計することをお勧めします。これは、スケッチに対する将来の変更、または予期しない実行時の状況が、メモリの問題を引き起こさないことを意味します。
私はあなたがそれを使うべきではないと思う人や、一般的には不要だと思う人に同意しません。あなたはそれの内と外を知らない場合、それは危険であると信じていますが、それは便利です。構造体またはバッファのサイズが(コンパイル時または実行時に)わからない(そして気にする必要がない)場合があります。特に、私が世界に送り出すライブラリの場合です。アプリケーションが既知の単一の構造のみを処理している場合は、コンパイル時にそのサイズでベークするだけでよいことに同意します。
例:任意の長さのデータペイロード(構造体、uint16_tの配列など)を受け取ることができるシリアルパケットクラス(ライブラリ)があります。そのクラスの送信側で、Packet.send()メソッドに、送信したいもののアドレスと、それを送信したいHardwareSerialポートを単に伝えるだけです。ただし、受信側では、受信ペイロードを保持するために動的に割り当てられた受信バッファーが必要です。そのペイロードは、たとえば、アプリケーションの状態に応じて、任意の時点で異なる構造になる可能性があるためです。単一の構造体を前後に送信するだけの場合は、コンパイル時に必要なサイズのバッファーを作成します。ただし、パケットの長さが時間とともに異なる場合、malloc()とfree()はそれほど悪くありません。
次のコードを使用してテストを数日間実行し、継続的にループさせましたが、メモリの断片化の証拠は見つかりませんでした。動的に割り当てられたメモリを解放すると、空き量は以前の値に戻ります。
// found at learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
uint8_t *_tester;
while(1) {
uint8_t len = random(1, 1000);
Serial.println("-------------------------------------");
Serial.println("len is " + String(len, DEC));
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("alloating _tester memory");
_tester = (uint8_t *)malloc(len);
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("Filling _tester");
for (uint8_t i = 0; i < len; i++) {
_tester[i] = 255;
}
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("freeing _tester memory");
free(_tester); _tester = NULL;
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
delay(1000); // quick look
}
RAMの劣化や、この方法を使用して動的に割り当てる能力の低下は見られなかったため、実行可能なツールだと思います。FWIW。
Arduinoでmalloc()とfree()を使用するのは本当に悪い考えですか?
簡単な答えはイエスです。理由は次のとおりです。
MPUとは何か、利用可能なリソースの制約内でプログラムする方法を理解することがすべてです。Arduino Unoは、32KB ISPフラッシュメモリ、1024B EEPROM、および2KB SRAMを備えたATmega328p MPUを使用します。それは多くのメモリリソースではありません。
2KB SRAMは、すべてのグローバル変数、文字列リテラル、スタック、およびヒープの使用可能性に使用されることに注意してください。スタックには、ISR用のヘッドルームも必要です。
メモリレイアウトは次のとおりです。
今日のPC /ラップトップのメモリ量は1.000.000倍以上です。スレッドごとの1Mバイトのデフォルトスタックスペースは珍しくありませんが、MPUでは完全に非現実的です。
組み込みソフトウェアプロジェクトでは、リソースの予算が必要です。これは、ISRレイテンシ、必要なメモリスペース、計算能力、命令サイクルなどを推定しています。残念ながら、無料のランチはなく、ハードリアルタイムの組み込みプログラミングは、プログラミングスキルの中で最も習得が困難です。
わかりました、これは古い質問であることは知っていますが、答えをよく読めば読むほど、顕著に見える観察に戻ってきます。
ここにチューリングの停止問題とのリンクがあるようです。動的割り当てを許可すると、「停止」の確率が高くなるため、質問はリスク許容度の1つになります。malloc()
失敗などの可能性を振り切るのは便利ですが、それでも有効な結果です。OPが尋ねる質問は、テクニックに関するものであるように見えます。はい、使用されるライブラリの詳細または特定のMPUが重要です。会話は、プログラムが停止したり、その他の異常終了したりするリスクを減らすことになります。私たちは、リスクを大きく異なって許容する環境の存在を認識する必要があります。LEDストリップにきれいな色を表示する私の趣味のプロジェクトは、何か異常なことが起こっても誰かを殺すことはありませんが、心肺マシン内のMCUはそうするでしょう。
LEDストリップでは、ロックするかどうかは気にしません。リセットするだけです。私はそれの結果がロックアップまたは動作に失敗MCUによって制御される人工心肺装置にあった場合は、文字通り生死あるのでに関する質問malloc()
とfree()
氏を発揮する可能性がどのように意図したプログラムのお得な情報間の分割をする必要がありますチューリングの有名な問題。それが数学的証明であることを忘れがちであり、十分に賢い場合にのみ計算の限界の犠牲者を避けることができると確信することは簡単です。
この質問には2つの回答が必要です。1つは顔の停止問題を凝視する際に瞬きを強いられる人向け、もう1つは他のすべての人向けです。arduinoのほとんどの用途は、ミッションクリティカルなアプリケーションや生死にかかわるアプリケーションではない可能性がありますが、コーディングするMPUに関係なく、その区別は依然として存在します。
いいえ。ただし、割り当てられたメモリのfree()に関しては、慎重に使用する必要があります。一般にソフトウェア開発と互換性のないレベルの無能さを意味するため、人々が直接メモリ管理を避けるべきだと言う理由を私は理解したことがありません。
arduinoを使用してドローンを制御するとしましょう。コードのあらゆる部分にエラーがあると、コードが空から落ちて誰かまたは何かを傷つける可能性があります。言い換えれば、誰かがmallocを使用する能力を欠いている場合、小さなバグが深刻な問題を引き起こす可能性のある他の領域が非常に多いため、コーディングするべきではありません。
mallocによって引き起こされるバグを追跡して修正するのは難しいですか?はい、しかしそれはリスクというよりもコーダー側のフラストレーションの問題です。リスクに関する限り、コードが正しく実行されていることを確認するための手順を実行しないと、コードのどの部分もmallocと同等またはそれ以上にリスクが高くなります。