wono on Nostr: Arduino/ESP32の String ...
Arduino/ESP32の String 連結で問題になるのは、文字列を書き換えるたびにメモリ確保・コピー・解放が発生することがあるためです。
例えばこのコード:
message += "🌡️ 温度: " + String(currentTemperature, 1) + "°C\n";
内部では単純に「後ろに文字を足す」だけではありません。
概念的には以下のような処理になります。
"🌡️ 温度: "
↓
String(currentTemperature, 1)
↓
"🌡️ 温度: 25.3"
↓
"🌡️ 温度: 25.3°C\n"
↓
messageへコピー
途中で一時的なStringが作られます。
さらに、このような書き方:
message += "abc";
message += "def";
message += "ghi";
なら比較的安全です。
しかし、
message += "abc" + String(value) + "def" + String(value2);
のような式では、
String(value)
↓
一時String生成
+ "def"
↓
別の一時String生成
+ String(value2)
↓
さらに一時String生成
という流れになります。
ESP32ではRAMはこのように使われます。
ヒープ領域
┌──────────────┐
│ message │ ← 本体
├──────────────┤
│ 一時String A │
├──────────────┤
│ 一時String B │
├──────────────┤
│ 一時String C │
└──────────────┘
処理終了後、一時Stringは解放されます。
問題はESP32のような小さいRAMでは、
使う
解放する
また確保する
を繰り返すと、**メモリの断片化(heap fragmentation)**が起きることです。
例:
最初:
空き 100KB
████████████████
小さいStringを作る:
████□□████████
解放:
████ □□ ████
合計では空きがあるのに、大きな連続領域が取れない、という状態になります。
対策が reserve() です。
現在:
String message = "";
message += "温度";
message += "湿度";
message += "気圧";
の場合、最初は小さい領域しか確保されません。
例えば:
message
容量 16byte
↓ 足りない
容量 32byteへ移動
↓ 足りない
容量 64byteへ移動
となります。
これを:
String message;
message.reserve(200);
にすると、
最初から:
message
容量 200byte
を確保します。
ただし、今回のコードの場合、もっと重要なのはここです。
String noteString = nostr.getNote(nsecHex, npubHex, now, message);
この時点で、
message
+
NostrイベントJSON
+
署名処理用データ
+
noteString
が同時に存在します。
つまり、投稿文を100文字増やしただけでも、実際にはその数倍のRAM消費増加になる可能性があります。
Nostrのような署名付き通信では、普通の「長い文章を送る」よりメモリ負荷が大きくなります。
Arduino/ESP32の String 連結で問題になるのは、文字列を書き換えるたびにメモリ確保・コピー・解放が発生することがある
発生することがある
って絶対ではないの?
はい、「発生することがある」と書いたのは、毎回必ず再確保が起きるわけではないためです。
ArduinoのStringは内部で容量(capacity)を持っています。
例:
String message;
message += "abc";
最初は内部バッファを確保します。
message
[abc.............]
その後:
message += "def";
追加分が現在の空き容量内なら、
[abcdef.........]
となり、再確保は発生しません。
しかし:
String message;
message += "abcdefghijklmnopqrstuvwxyz";
のように容量を超えると、
新しい大きいメモリ領域を確保
古い内容をコピー
古い領域を解放
が発生します。
古い領域
[abc]
↓
新しい領域
[abcdefghijklmnopqrstuvwxyz]
↓
古い領域解放
です。
今回のコードの場合:
String message = "";
message += "🌡️ 温度: " + String(currentTemperature, 1) + "°C\n";
message += "💧 湿度: " + String(currentHumidity, 1) + "%\n";
message += "📊 気圧: " + String(currentPressure, 1) + "hPa";
では、再確保が起きる可能性は高めです。
理由は、
最初の容量がほぼ0
追加する文字列が複数回ある
日本語・絵文字はUTF-8で1文字数バイトになる
String(float)で一時オブジェクトが作られる
ためです。
ただし、ESP32のArduino String実装では容量拡張時にある程度余裕を持って確保するため、毎回1文字追加ごとに確保するわけではありません。
ちなみにもっと正確に言うと、
message += "固定文字"
→ 容量不足時だけ再確保
"文字列" + String(数値)
→ 一時的なString生成が発生する可能性が高い
message.reserve(200)済み
→ 200バイト以内なら再確保なし
です。
なので問題の中心は「+=を使うこと」ではなく、容量を見積もらずに何度も拡張させることと、一時Stringを大量に作る書き方です。
Published at
2026-08-03 01:03:08 UTCEvent JSON
{
"id": "dfc2bca0b437cd9e039cee24f2e29f35758bbca3252aff3483dfa2c8b78b7567",
"pubkey": "6f849dc7387f4dc991af2360b5cbe61e9fc96170a096fa577c1b9177ca5882f5",
"created_at": 1785718988,
"kind": 1,
"tags": [
[
"client",
"lumilumi",
"31990:84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5:1727506446612",
"wss://cagliostr.compile-error.net"
]
],
"content": "Arduino/ESP32の String 連結で問題になるのは、文字列を書き換えるたびにメモリ確保・コピー・解放が発生することがあるためです。\n\n例えばこのコード:\n\nmessage += \"🌡️ 温度: \" + String(currentTemperature, 1) + \"°C\\n\";\n\n内部では単純に「後ろに文字を足す」だけではありません。\n\n概念的には以下のような処理になります。\n\n\"🌡️ 温度: \"\n ↓\nString(currentTemperature, 1)\n ↓\n\"🌡️ 温度: 25.3\"\n ↓\n\"🌡️ 温度: 25.3°C\\n\"\n ↓\nmessageへコピー\n\n途中で一時的なStringが作られます。\n\nさらに、このような書き方:\n\nmessage += \"abc\";\nmessage += \"def\";\nmessage += \"ghi\";\n\nなら比較的安全です。\n\nしかし、\n\nmessage += \"abc\" + String(value) + \"def\" + String(value2);\n\nのような式では、\n\nString(value)\n↓\n一時String生成\n\n+ \"def\"\n↓\n別の一時String生成\n\n+ String(value2)\n↓\nさらに一時String生成\n\nという流れになります。\n\nESP32ではRAMはこのように使われます。\n\nヒープ領域\n\n┌──────────────┐\n│ message │ ← 本体\n├──────────────┤\n│ 一時String A │\n├──────────────┤\n│ 一時String B │\n├──────────────┤\n│ 一時String C │\n└──────────────┘\n\n処理終了後、一時Stringは解放されます。\n\n問題はESP32のような小さいRAMでは、\n\n使う\n解放する\nまた確保する\n\nを繰り返すと、**メモリの断片化(heap fragmentation)**が起きることです。\n\n例:\n\n最初:\n\n空き 100KB\n████████████████\n\n小さいStringを作る:\n\n████□□████████\n\n解放:\n\n████ □□ ████\n\n合計では空きがあるのに、大きな連続領域が取れない、という状態になります。\n\n対策が reserve() です。\n\n現在:\n\nString message = \"\";\nmessage += \"温度\";\nmessage += \"湿度\";\nmessage += \"気圧\";\n\nの場合、最初は小さい領域しか確保されません。\n\n例えば:\n\nmessage\n容量 16byte\n↓ 足りない\n容量 32byteへ移動\n↓ 足りない\n容量 64byteへ移動\n\nとなります。\n\nこれを:\n\nString message;\nmessage.reserve(200);\n\nにすると、\n\n最初から:\n\nmessage\n容量 200byte\n\nを確保します。\n\nただし、今回のコードの場合、もっと重要なのはここです。\n\nString noteString = nostr.getNote(nsecHex, npubHex, now, message);\n\nこの時点で、\n\nmessage\n+\nNostrイベントJSON\n+\n署名処理用データ\n+\nnoteString\n\nが同時に存在します。\n\nつまり、投稿文を100文字増やしただけでも、実際にはその数倍のRAM消費増加になる可能性があります。\n\nNostrのような署名付き通信では、普通の「長い文章を送る」よりメモリ負荷が大きくなります。\n\nArduino/ESP32の String 連結で問題になるのは、文字列を書き換えるたびにメモリ確保・コピー・解放が発生することがある\n\n発生することがある\nって絶対ではないの?\n\nはい、「発生することがある」と書いたのは、毎回必ず再確保が起きるわけではないためです。\n\nArduinoのStringは内部で容量(capacity)を持っています。\n\n例:\n\nString message;\nmessage += \"abc\";\n\n最初は内部バッファを確保します。\n\nmessage\n[abc.............]\n\nその後:\n\nmessage += \"def\";\n\n追加分が現在の空き容量内なら、\n\n[abcdef.........]\n\nとなり、再確保は発生しません。\n\nしかし:\n\nString message;\nmessage += \"abcdefghijklmnopqrstuvwxyz\";\n\nのように容量を超えると、\n\n新しい大きいメモリ領域を確保\n古い内容をコピー\n古い領域を解放\n\nが発生します。\n\n古い領域\n[abc]\n\n↓\n\n新しい領域\n[abcdefghijklmnopqrstuvwxyz]\n\n↓\n\n古い領域解放\n\nです。\n\n今回のコードの場合:\n\nString message = \"\";\n\nmessage += \"🌡️ 温度: \" + String(currentTemperature, 1) + \"°C\\n\";\nmessage += \"💧 湿度: \" + String(currentHumidity, 1) + \"%\\n\";\nmessage += \"📊 気圧: \" + String(currentPressure, 1) + \"hPa\";\n\nでは、再確保が起きる可能性は高めです。\n\n理由は、\n\n最初の容量がほぼ0\n追加する文字列が複数回ある\n日本語・絵文字はUTF-8で1文字数バイトになる\nString(float)で一時オブジェクトが作られる\n\nためです。\n\nただし、ESP32のArduino String実装では容量拡張時にある程度余裕を持って確保するため、毎回1文字追加ごとに確保するわけではありません。\n\nちなみにもっと正確に言うと、\n\nmessage += \"固定文字\"\n→ 容量不足時だけ再確保\n\"文字列\" + String(数値)\n→ 一時的なString生成が発生する可能性が高い\nmessage.reserve(200)済み\n→ 200バイト以内なら再確保なし\n\nです。\n\nなので問題の中心は「+=を使うこと」ではなく、容量を見積もらずに何度も拡張させることと、一時Stringを大量に作る書き方です。",
"sig": "c824b0e2ac94faa9594fba395764bda58775e41b5479e3f95a5c56ae66770ca4b3cbf863be2b77e425b4c7434b5079f2409176c10254c1f31d1c5c8066cd94dd"
}