การใช้งานmalloc()
และfree()
ดูเหมือนว่าค่อนข้างหายากในโลก Arduino มันถูกใช้ใน AVR C บริสุทธิ์บ่อยกว่า แต่ก็ยังมีความระมัดระวัง
มันเป็นความคิดที่ดีที่จะใช้malloc()
และfree()
กับ Arduino?
การใช้งานmalloc()
และfree()
ดูเหมือนว่าค่อนข้างหายากในโลก Arduino มันถูกใช้ใน AVR C บริสุทธิ์บ่อยกว่า แต่ก็ยังมีความระมัดระวัง
มันเป็นความคิดที่ดีที่จะใช้malloc()
และfree()
กับ Arduino?
คำตอบ:
กฎทั่วไปของฉันสำหรับระบบฝังตัวเป็นเพียงบัฟเฟอร์ขนาดใหญ่และเพียงครั้งเดียวในช่วงเริ่มต้นของโปรแกรมเช่นในmalloc()
setup()
ปัญหาเกิดขึ้นเมื่อคุณจัดสรรและจัดสรรหน่วยความจำ ในช่วงระยะเวลานานหน่วยความจำจะกระจัดกระจายและในที่สุดการจัดสรรล้มเหลวเนื่องจากไม่มีพื้นที่ว่างขนาดใหญ่เพียงพอแม้ว่าหน่วยความจำว่างทั้งหมดจะเพียงพอสำหรับคำขอ
(เปอร์สเปคทีฟประวัติให้ข้ามหากไม่สนใจ): ขึ้นอยู่กับการนำไปใช้ของโหลดเดอร์ข้อได้เปรียบเพียงอย่างเดียวของการจัดสรรแบบรันไทม์กับการจัดสรรเวลาคอมไพล์ (globals แบบเน้นข้อความ) คือขนาดของไฟล์ hex เมื่อระบบฝังตัวถูกสร้างโดยไม่ใช้คอมพิวเตอร์ชั้นวางที่มีหน่วยความจำระเหยโปรแกรมมักจะถูกอัพโหลดไปยังระบบฝังตัวจากเครือข่ายหรือคอมพิวเตอร์เครื่องมือและเวลาในการอัปโหลดบางครั้งก็เป็นปัญหา การออกจากบัฟเฟอร์ที่เต็มไปด้วยเลขศูนย์จากรูปภาพอาจทำให้เวลาสั้นลงอย่างมาก)
หากฉันต้องการการจัดสรรหน่วยความจำแบบไดนามิกในระบบฝังตัวฉันโดยทั่วไปmalloc()
หรือโดยเฉพาะอย่างยิ่งการจัดสรรแบบคงที่สระว่ายน้ำขนาดใหญ่และแบ่งออกเป็นบัฟเฟอร์ขนาดคงที่ (หรือหนึ่งสระว่ายน้ำแต่ละบัฟเฟอร์ขนาดเล็กและขนาดใหญ่ตามลำดับ) ยกเลิกการจัดสรรจากกลุ่มนั้น จากนั้นทุกคำขอสำหรับจำนวนหน่วยความจำสูงสุดจนถึงขนาดบัฟเฟอร์คงที่จะได้รับเกียรติด้วยหนึ่งในบัฟเฟอร์เหล่านั้น ฟังก์ชั่นการโทรไม่จำเป็นต้องรู้ว่ามันใหญ่กว่าที่ร้องขอหรือไม่และโดยการหลีกเลี่ยงการแยกและรวมบล็อกใหม่ แน่นอนว่าการรั่วไหลของหน่วยความจำยังคงเกิดขึ้นได้หากโปรแกรมได้จัดสรร / ยกเลิกการจัดสรรบั๊ก
โดยทั่วไปเมื่อเขียนสเก็ตช์ Arduino คุณจะหลีกเลี่ยงการจัดสรรแบบไดนามิก (ไม่ว่าจะด้วยmalloc
หรือnew
สำหรับอินสแตนซ์ C ++) ผู้คนค่อนข้างใช้static
ตัวแปรglobal หรือตัวแปร local (stack)
การใช้การจัดสรรแบบไดนามิกอาจทำให้เกิดปัญหาหลายประการ:
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
ขนาดแตกต่างกันสำหรับแต่ละคน) แต่เราอาจมีปัญหาการกระจายตัวของฮีป
หมายเหตุการใช้ destructor เพื่อให้แน่ใจว่าหน่วยความจำที่จัดสรรแบบไดนามิกสำหรับ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);
}
หากไม่มี malloc อยู่ภายในdo_whatever_with()
หรือถ้าฟังก์ชันนั้นปลดปล่อยสิ่งที่จัดสรรไว้คุณก็ปลอดภัยจากการแตกแฟรกเมนต์
นี่เป็นลักษณะทั่วไปของสองกรณีก่อนหน้านี้ หากคุณใช้ฮีปเหมือนสแต็ก (เข้ามาก่อนจะออกมาก่อน) จากนั้นมันจะทำงานเหมือนสแต็กและไม่ใช่แฟรกเมนต์ realloc()
มันควรจะตั้งข้อสังเกตว่าในกรณีนี้มันมีความปลอดภัยในการปรับขนาดบัฟเฟอร์ที่ผ่านมากับการจัดสรร
นี้จะไม่ป้องกันการกระจายตัว แต่มันมีความปลอดภัยในแง่ที่ว่ากองจะไม่เติบโตมีขนาดใหญ่กว่าสูงสุดที่ใช้ขนาด หากบัฟเฟอร์ทั้งหมดของคุณมีขนาดเท่ากันคุณสามารถมั่นใจได้ว่าเมื่อใดก็ตามที่คุณว่างหนึ่งในนั้นจะมีสล็อตสำหรับการจัดสรรในภายหลัง
การใช้การจัดสรรแบบไดนามิก (ผ่านmalloc
/ free
หรือnew
/ delete
) ไม่ได้เลวร้ายอย่างที่เป็นเช่นนี้ อันที่จริงแล้วสำหรับบางอย่างเช่นการประมวลผลสตริง (เช่นผ่านString
วัตถุ) มักจะมีประโยชน์มาก นั่นเป็นเพราะสเก็ตช์จำนวนมากใช้ชิ้นส่วนเล็ก ๆ ของสตริงซึ่งในที่สุดจะรวมกันเป็นส่วนที่ใหญ่กว่า การใช้การจัดสรรแบบไดนามิกช่วยให้คุณใช้หน่วยความจำได้มากเท่าที่คุณต้องการสำหรับแต่ละหน่วย ในทางตรงกันข้ามการใช้บัฟเฟอร์คงที่ขนาดคงที่สำหรับแต่ละอันอาจทำให้สิ้นเปลืองพื้นที่มาก (ทำให้หน่วยความจำหมดเร็วขึ้นมาก) แม้ว่ามันจะขึ้นอยู่กับบริบททั้งหมด
จากข้อมูลทั้งหมดที่กล่าวมาเป็นสิ่งสำคัญมากเพื่อให้แน่ใจว่าการใช้งานหน่วยความจำสามารถคาดเดาได้ การอนุญาตให้ร่างภาพใช้หน่วยความจำตามอำเภอใจโดยขึ้นอยู่กับสถานการณ์ขณะทำงาน (เช่นอินพุต) สามารถทำให้เกิดปัญหาได้อย่างง่ายดายไม่ช้าก็เร็ว ในบางกรณีมันอาจปลอดภัยอย่างสมบูรณ์เช่นถ้าคุณรู้ว่าการใช้งานจะไม่เพิ่มขึ้นมาก ภาพร่างสามารถเปลี่ยนแปลงได้ในระหว่างขั้นตอนการเขียนโปรแกรม การสันนิษฐานในช่วงต้นอาจถูกลืมได้เมื่อมีบางสิ่งเปลี่ยนไปในภายหลังทำให้เกิดปัญหาที่ไม่คาดฝัน
เพื่อความแข็งแกร่งมักจะดีกว่าที่จะทำงานกับบัฟเฟอร์ขนาดคงที่ถ้าเป็นไปได้และออกแบบร่างให้ทำงานอย่างชัดเจนกับข้อ จำกัด เหล่านั้นตั้งแต่เริ่มแรก นั่นหมายถึงการเปลี่ยนแปลงร่างใด ๆ ในอนาคตหรือสถานการณ์รันไทม์ที่คาดไม่ถึงหวังว่าจะไม่ทำให้เกิดปัญหาหน่วยความจำ
ฉันไม่เห็นด้วยกับคนที่คิดว่าคุณไม่ควรใช้หรือโดยทั่วไปไม่จำเป็น ฉันเชื่อว่ามันอาจเป็นอันตรายได้หากคุณไม่ทราบรายละเอียด แต่ก็มีประโยชน์ ฉันมีหลายกรณีที่ฉันไม่รู้ (และไม่ควรสนใจ) ขนาดของโครงสร้างหรือบัฟเฟอร์ (ในเวลารวบรวมหรือเวลาทำงาน) โดยเฉพาะอย่างยิ่งเมื่อมันมาถึงห้องสมุดที่ฉันส่งออกไปสู่โลก ฉันยอมรับว่าหากแอปพลิเคชันของคุณเกี่ยวข้องกับโครงสร้างเดียวที่รู้จักคุณควรอบในขนาดนั้นในเวลารวบรวม
ตัวอย่าง: ฉันมีคลาสแพ็กเก็ตแบบอนุกรม (ไลบรารี) ที่สามารถรับข้อมูล payload ได้ตามอำเภอใจ (สามารถเป็นโครงสร้างอาร์เรย์ของ uint16_t เป็นต้น) ในตอนท้ายของการส่งของคลาสนั้นคุณเพียงแค่บอก Packet.send () วิธีการที่อยู่ของสิ่งที่คุณต้องการที่จะส่งและพอร์ต HardwareSerial ที่คุณต้องการที่จะส่ง อย่างไรก็ตามในตอนท้ายของการรับฉันต้องการบัฟเฟอร์การรับที่จัดสรรแบบไดนามิกเพื่อเก็บ payload ที่เข้ามาเนื่องจาก payload นั้นอาจเป็นโครงสร้างที่แตกต่างกันในช่วงเวลาใดก็ตามขึ้นอยู่กับสถานะของแอปพลิเคชัน ถ้าฉันเพิ่งส่งโครงสร้างเดียวกลับไปกลับมาฉันจะทำให้บัฟเฟอร์มีขนาดที่จะต้องรวบรวม แต่ในกรณีที่แพ็กเก็ตอาจมีความยาวต่างกันเมื่อเวลาผ่านไป 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
เป็นความคิดที่ดีจริงๆหรือไม่ที่จะใช้ malloc () และฟรี () กับ Arduino
คำตอบสั้น ๆ คือใช่ ด้านล่างนี้คือเหตุผลว่าทำไม:
มันคือทั้งหมดที่เกี่ยวกับการทำความเข้าใจว่า MPU คืออะไรและวิธีการโปรแกรมภายในข้อ จำกัด ของทรัพยากรที่มีอยู่ Arduino Uno ใช้ATmega328p MPU พร้อมหน่วยความจำแฟลช 32KB ISP, 1024B EEPROM และ SRK 2KB นั่นไม่ใช่ทรัพยากรหน่วยความจำจำนวนมาก
โปรดจำไว้ว่า 2KB SRAM ใช้สำหรับตัวแปรโกลบอลสตริงตัวอักษรสแต็กและการใช้ฮีปที่เป็นไปได้ทั้งหมด สแต็กยังต้องมีห้องหัวสำหรับ ISR
พีซี / แล็ปท็อปปัจจุบันมีหน่วยความจำมากกว่า 1,000,000 ครั้ง พื้นที่สแต็กเริ่มต้น 1 Mbyte ต่อเธรดไม่ใช่เรื่องแปลก แต่ไม่สมจริงทั้งหมดใน MPU
โครงการซอฟต์แวร์แบบฝังตัวต้องทำงบประมาณทรัพยากร นี่เป็นการประมาณเวลาแฝงของ ISR, พื้นที่หน่วยความจำที่จำเป็น, กำลังประมวลผล, วงจรการเรียนการสอน ฯลฯ โชคไม่ดีที่ไม่มีอาหารกลางวันและการเขียนโปรแกรมแบบเรียลไทม์แบบฝังตัวยากที่สุดของทักษะการเขียนโปรแกรม
ตกลงฉันรู้ว่านี่เป็นคำถามเก่า แต่ยิ่งฉันอ่านคำตอบมากเท่าไหร่ฉันก็ยิ่งกลับมาดูข้อสังเกตที่สำคัญ
ดูเหมือนจะมีลิงค์กับปัญหาการหยุดชะงักของทัวริงที่นี่ การอนุญาตให้การจัดสรรแบบไดนามิกเพิ่มโอกาสในการพูดว่า 'หยุด' ดังนั้นคำถามจะกลายเป็นหนึ่งในการยอมรับความเสี่ยง แม้ว่าจะสะดวกในการโบกมือให้เป็นไปได้ของmalloc()
ความล้มเหลวและอื่น ๆ แต่ก็ยังเป็นผลลัพธ์ที่ถูกต้อง คำถามที่ OP ถามดูเหมือนจะเป็นเรื่องเกี่ยวกับเทคนิคเท่านั้นและใช่รายละเอียดของห้องสมุดที่ใช้หรือ MPU เฉพาะนั้นมีความสำคัญ บทสนทนาจะลดความเสี่ยงของการหยุดโปรแกรมหรือสิ้นสุดผิดปกติอื่น ๆ เราจำเป็นต้องตระหนักถึงการมีอยู่ของสภาพแวดล้อมที่ทนต่อความเสี่ยงต่างกันอย่างมากมาย งานอดิเรกของฉันในการแสดงสีสวย ๆ บน LED-strip จะไม่ฆ่าใครถ้ามีอะไรผิดปกติเกิดขึ้น แต่ MCU ภายในเครื่องหัวใจปอดน่าจะเป็น
สำหรับ LED-strip ของฉันฉันไม่สนใจว่ามันจะล็อคฉันจะรีเซ็ตมัน ถ้าฉันอยู่บนเครื่องหัวใจ - หัวใจที่ถูกควบคุมโดย MCU ผลที่ตามมาของการล็อคหรือล้มเหลวในการทำงานคือชีวิตและความตายดังนั้นคำถามเกี่ยวกับmalloc()
และfree()
ควรแยกระหว่างวิธีที่โปรแกรมตั้งใจจัดการกับความเป็นไปได้ที่จะแสดงให้เห็นว่านาย ปัญหาที่โด่งดังของทัวริง มันง่ายที่จะลืมว่ามันเป็นข้อพิสูจน์ทางคณิตศาสตร์และเพื่อโน้มน้าวตัวเราเองว่าหากเราฉลาดเท่านั้นเราสามารถหลีกเลี่ยงการสูญเสียขีด จำกัด ของการคำนวณ
คำถามนี้ควรมีคำตอบที่ยอมรับสองคำตอบหนึ่งข้อสำหรับผู้ที่ถูกบังคับให้กระพริบตาเมื่อจ้องมองปัญหา Halting ในหน้าและอีกคำตอบหนึ่งสำหรับผู้อื่นทั้งหมด ในขณะที่การใช้งานส่วนใหญ่ของ Arduino นั้นไม่น่าจะเป็นภารกิจสำคัญหรือแอปพลิเคชันที่ใช้ชีวิตและความตายความแตกต่างยังคงอยู่ที่นั่นโดยไม่คำนึงถึง MPU ที่คุณอาจเข้ารหัส
ไม่ได้ แต่จะต้องใช้อย่างระมัดระวังเกี่ยวกับการฟรี () ในการจัดสรรหน่วยความจำ ฉันไม่เคยเข้าใจว่าทำไมคนพูดว่าการจัดการหน่วยความจำโดยตรงควรหลีกเลี่ยงเพราะมันหมายถึงระดับของความสามารถที่ไม่สอดคล้องกับการพัฒนาซอฟต์แวร์โดยทั่วไป
ให้บอกว่าคุณใช้ Arduino ของคุณเพื่อควบคุมเสียงพึมพำ ข้อผิดพลาดใด ๆ ในส่วนใด ๆ ของรหัสของคุณอาจทำให้รหัสนี้หลุดออกมาจากท้องฟ้าและทำร้ายใครบางคนหรือบางสิ่งบางอย่าง กล่าวอีกนัยหนึ่งถ้ามีคนขาดความสามารถในการใช้ malloc พวกเขาอาจไม่ควรเข้ารหัสเพราะมีพื้นที่อื่น ๆ มากมายที่มีข้อบกพร่องเล็ก ๆ อาจทำให้เกิดปัญหาร้ายแรง
ข้อบกพร่องที่เกิดจาก malloc ยากที่จะติดตามและแก้ไขหรือไม่ ใช่ แต่นั่นเป็นเรื่องของความคับข้องใจในตัวแปลงสัญญาณมากกว่าความเสี่ยง เท่าที่ความเสี่ยงเกิดขึ้นส่วนใด ๆ ของรหัสของคุณอาจมีความเสี่ยงเท่ากันหรือมากกว่า malloc หากคุณไม่ทำตามขั้นตอนเพื่อให้แน่ใจว่าถูกต้อง