שְׁאֵלָה:
האם שימוש ב- malloc () ובחינם () הוא רעיון רע מאוד בארדואינו?
Cybergibbons
2014-03-09 14:00:25 UTC
view on stackexchange narkive permalink

השימוש ב- malloc () וב- free () נראה די נדיר בעולם הארדואינו. הוא משמש ב- AVR C טהור לעיתים קרובות יותר, אך עדיין בזהירות.

האם זה רעיון רע באמת להשתמש ב malloc () ו- free () קוד> עם ארדואינו?

יהיה לך אזול הזיכרון ממש מהר אחרת, ואם אתה יודע בכמה זיכרון תשתמש, אתה יכול להקצות אותו באופן סטטי בכל מקרה
אני לא יודע אם זה * גרוע *, אבל אני חושב שזה לא משמש כי אתה כמעט אף פעם לא נגמר זיכרון RAM במשך רוב הסקיצות וזה פשוט בזבוז של פלאש ומחזורי שעון יקרים. כמו כן, אל תשכח מהיקף (אם כי אני לא יודע אם שטח זה עדיין מוקצה לכל המשתנים).
כרגיל, התשובה הנכונה היא "זה תלוי". לא סיפקת מספיק מידע כדי לדעת בוודאות אם הקצאה דינמית מתאימה לך.
שמונה תשובות:
#1
+43
JRobert
2014-03-11 21:36:25 UTC
view on stackexchange narkive permalink

הכלל הכללי שלי למערכות משובצות הוא רק malloc () מאגרים גדולים ורק פעם אחת, בתחילת התוכנית, למשל, ב setup () . הצרה מתרחשת כשאתה מקצה ומבטל הקצאה של זיכרון. במהלך הפעלה ארוכת טווח, הזיכרון הופך למפוצל ובסופו של דבר ההקצאה נכשלת בגלל היעדר שטח פנוי מספיק גדול, למרות שהזיכרון החופשי הכולל הוא יותר ממספיק לבקשה.

(נקודת מבט היסטורית, דלג אם אינך מעוניין): בהתאם ליישום המטעין, היתרון היחיד של הקצאת זמן ריצה לעומת הקצאת זמן קומפילציה (גלובלים אינטליזציה) הוא גודל קובץ ה- hex. כאשר נבנו מערכות משובצות עם מחשבים מהמדף עם כל הזיכרון ההפכפך, התוכנית הועלתה לרוב למערכת המוטמעת מרשת או ממחשב מכשור ולעיתים זמן ההעלאה היה בעיה. השארת מאגרים מלאים באפסים מהתמונה עשויה לקצר את הזמן באופן משמעותי.)

אם אני זקוק להקצאת זיכרון דינמית במערכת משובצת, אני בדרך כלל malloc () , או רצוי, הקצה סטטית, בריכה גדולה וחלק אותה למאגרים בגודל קבוע (או בריכה אחת כל אחת ממאגרים קטנים וגדולים, בהתאמה) ועשה את ההקצאה / הקצאה שלי מהמאגר הזה. ואז כל בקשה לכמות כלשהי של זיכרון עד גודל המאגר הקבוע מכובדת באחד מאותם מאגרים. פונקציית הקריאה לא צריכה לדעת אם היא גדולה מהמתבקש, ועל ידי הימנעות מפיצול ושילוב מחדש של בלוקים אנו פותרים פיצול. כמובן שעדיין דליפות זיכרון יכולות להתרחש אם בתוכנית יש הקצאת / ביטול הקצאת באגים.

הערה היסטורית נוספת, זה הוביל במהירות לקטע BSS, שאיפשר לתוכנית לאפס את הזיכרון שלה לאתחול, מבלי להעתיק לאט את האפסים במהלך טעינת התוכנית.
#2
+18
Edgar Bonet
2015-03-01 19:51:45 UTC
view on stackexchange narkive permalink

בדקתי את האלגוריתם בו השתמש malloc () , מ- avr-libc, ונראה שיש כמה דפוסי שימוש בטוחים מבחינת פרגמנטציה רבה:

1. הקצה רק מאגרים ארוכי טווח

כוונתי לכך: הקצה את כל מה שאתה צריך בתחילת התוכנית, ולעולם לא תשחרר אותה. כמובן שבמקרה זה, אתה יכול גם להשתמש בבליקויים סטטיים ...

2. הקצה מאגרים קצרי טווח בלבד

המשמעות: אתה משחרר את המאגר לפני שתקצה משהו אחר. דוגמא לתחום עשויה להיראות כך:

void foo () {size_t size = figure_out_needs (); char * buffer = malloc (גודל); אם (! חיץ) נכשל (); do_whatever_with (buffer); free (buffer);}

אם אין malloc בתוך do_whatever_with () , או אם הפונקציה הזו חופשית כל מה שהיא מקצה, אתה בטוח מפני פיצול.

3. שחרר תמיד את המאגר שהוקצה לאחרונה

זו הכללה של שני המקרים הקודמים. אם אתה משתמש ב- heaplike בערימה (האחרון ב- הוא הראשון בחוץ), אז הוא יתנהג כמו מחסנית ולא קטע. יש לציין כי במקרה זה בטוח מגדילים את המאגר שהוקצה לאחרונה באמצעות realloc().

4. הקצה תמיד את אותו הגודל

זה לא ימנע פיצול, אבל זה בטוח במובן שהערימה לא תגדל מגודל ה בשימוש המרבי. אם לכל המאגרים שלך אותו גודל, אתה יכול להיות בטוח שבכל פעם שאתה משחרר אחד מהם, החריץ יהיה זמין להקצאות עוקבות.

יש להימנע מתבנית 2 מכיוון שהיא מוסיפה מחזורים למאלוק () ולחינם () כאשר ניתן לעשות זאת באמצעות "חיץ char [גודל];" (ב- C ++). ברצוני להוסיף גם את האנטי-דפוס "אף פעם מ- ISR".
#3
+17
jfpoilpret
2014-03-09 22:07:19 UTC
view on stackexchange narkive permalink

בדרך כלל, בעת כתיבת שרטוטים של Arduino, תימנע מהקצאה דינמית (יהיה זה עם malloc או new עבור מקרי C ++), אנשים מעדיפים להשתמש גלובלי-או סטטי - משתנים, או משתנים מקומיים (מחסנית).

שימוש בהקצאה דינמית יכול להוביל למספר בעיות:

  • דליפות זיכרון (אם אתה מאבד מצביע ל זיכרון שהקצית בעבר, או יותר סביר אם תשכח לפנות את הזיכרון שהוקצה כאשר אינך זקוק לו יותר)
  • פיצול ערימה (לאחר מספר malloc / שיחות חינם בהן הערימה גדלה מכמות הזיכרון האמיתית המוקצה כרגע

ברוב המצבים איתם התמודדתי, הקצאה דינמית לא הייתה נחוצה, או שניתן היה להימנע ממנה פקודות מאקרו כמו בדוגמת הקוד הבאה:

MySketch.ino

  #define BUFFER_SIZE 32 # include "Dummy.h"  

דמה.ה

  דמה מחלקה {חיץ בתים [BUFFER_SIZE]; ...};  

ללא #define BUFFER_SIZE , אם היינו רוצים שבמחלקה Dummy יהיה מאגר לא קבוע בגודל , נצטרך להשתמש בהקצאה דינמית באופן הבא:

  class Dummy {const byte * buffer; ציבורי: דמה (גודל int): מאגר (בית חדש [גודל]) {} ~ דמה () {מחק [] bufer; }};  

במקרה זה, יש לנו יותר אפשרויות מאשר במדגם הראשון (למשל, השתמש באובייקטים אחרים Dummy עם מאגר שונה גודל עבור כל אחד), אך יתכן שיש לנו בעיות של פיצול ערימה.

שימו לב שהשימוש במורס כדי להבטיח זיכרון שהוקצה באופן דינמי ל חיץ קוד> ישוחרר כאשר דמה מופע נמחק.

#4
+9
Peter Bloomfield
2014-03-09 23:32:04 UTC
view on stackexchange narkive permalink

שימוש בהקצאה דינמית (באמצעות malloc / free או new / delete ) אינו מטבעו רע כגון. למעשה, עבור משהו כמו עיבוד מחרוזות (למשל באמצעות האובייקט String ), זה לרוב מועיל למדי. הסיבה לכך היא ששרטוטים רבים משתמשים בכמה שברים קטנים של מיתרים, שבסופו של דבר מתאחדים לגדול יותר. שימוש בהקצאה דינמית מאפשר לך להשתמש רק בכמות הזיכרון שאתה זקוק לכל אחד. לעומת זאת, שימוש במאגר סטטי בגודל קבוע לכל אחד יכול בסופו של דבר לבזבז הרבה מקום (לגרום לו להיגמר הזיכרון הרבה יותר מהר), אם כי זה תלוי לחלוטין בהקשר.

עם הכל עם זאת, חשוב מאוד לוודא שניתן יהיה לצפות מראש את השימוש בזיכרון. מתן אפשרות לשרטוט להשתמש בכמויות שרירותיות של זיכרון בהתאם לנסיבות זמן הריצה (למשל קלט) יכולה לגרום בקלות לבעיה במוקדם או במאוחר. במקרים מסוימים זה עשוי להיות בטוח לחלוטין, למשל. אם אתה יודע השימוש לעולם לא יסתכם בהרבה. סקיצות יכולות להשתנות במהלך תהליך התכנות. הנחה שנעשתה בשלב מוקדם יכולה להישכח כשמשהו משתנה מאוחר יותר, וכתוצאה מכך לבעיה בלתי צפויה.

למען חוסן, בדרך כלל עדיף לעבוד עם מאגרים בגודל קבוע במידת האפשר, ולעצב את השרטוט לעבודה במפורש עם המגבלות הללו מלכתחילה. פירוש הדבר שכל שינוי עתידי במערכון, או כל נסיבות זמן ריצה בלתי צפויות, לא אמור לגרום לבעיות זיכרון.

#5
+8
StuffAndyMakes
2015-05-18 19:49:12 UTC
view on stackexchange narkive permalink

אני לא מסכים עם אנשים שחושבים שאתה לא צריך להשתמש בו או שזה בדרך כלל מיותר. אני מאמין שזה יכול להיות מסוכן אם אתה לא מכיר את העניין והעניין, אבל זה שימושי. יש לי מקרים שבהם אני לא יודע (ולא צריך לדאוג לדעת) את גודל המבנה או המאגר (בזמן הידור או זמן ריצה), במיוחד כשמדובר בספריות שאני שולח לעולם. אני מסכים שאם היישום שלך מתמודד רק עם מבנה ידוע יחיד, עליך פשוט לאפות בגודל זה בזמן הידור.

דוגמה: יש לי מחלקה חבילה טורית (ספרייה) שיכולה קח עומסי מטען של נתונים באורך שרירותי (יכול להיות מבנה, מערך של uint16_t וכו '). בסוף השליחה של אותה שיעור אתה פשוט אומר לשיטת Packet.send () את הכתובת של הדבר שאתה רוצה לשלוח ואת יציאת HardwareSerial שדרכה אתה רוצה לשלוח אותו. עם זאת, בקצה הקבלה אני זקוק למאגר קבלה שהוקצה באופן דינמי כדי להחזיק את המטען הנכנס, מכיוון שמטען זה יכול להיות מבנה אחר בכל רגע נתון, תלוי במצב היישום, למשל. אם אני רק שולח מבנה יחיד קדימה ואחורה, הייתי פשוט הופך את המאגר לגודל שהוא צריך להיות בזמן הקומפילציה. אבל, במקרה שחבילות עשויות להיות באורכים שונים לאורך זמן, malloc () וחינם () אינן כל כך גרועות.

הפעלתי בדיקות עם הקוד הבא במשך ימים, והנחתי לו לולאה ברציפות, ולא מצאתי שום עדות לפיצול זיכרון. לאחר שחרור הזיכרון שהוקצה באופן דינמי, הסכום החופשי חוזר לערכו הקודם.

  // נמצא ב- learn.adafruit.com/memories-of-an-arduino/measuring-free-memoryint freeRam () {extern int __heap_start, * __ brkval; בטלוויזיה; return (int) &v - (__brkval == 0? (int) &__heap_start: (int) __brkval);} uint8_t * _tester; while (1) {uint8_t len ​​= אקראי (1, 1000); Serial.println ("-------------------------------------------"); Serial.println ("len is" + מחרוזת (len, DEC)); Serial.println ("RAM:" + מחרוזת (freeRam (), DEC));
Serial.println ("_ tester =" + מחרוזת ((uint16_t) _tester, DEC)); Serial.println ("הקצאת זיכרון _טסטר"); _טסטר = (uint8_t *) malloc (len); Serial.println ("RAM:" + מחרוזת (freeRam (), DEC)); Serial.println ("_ tester =" + מחרוזת ((uint16_t) _tester, DEC)); Serial.println ("מילוי _טסטר"); עבור (uint8_t i = 0; i < len; i ++) {_tester [i] = 255; } Serial.println ("RAM:" + מחרוזת (freeRam (), DEC)); Serial.println ("שחרור זיכרון _טסטר"); חינם (_טסטר); _טסטר = NULL; Serial.println ("RAM:" + מחרוזת (freeRam (), DEC)); Serial.println ("_ tester =" + מחרוזת ((uint16_t) _tester, DEC)); עיכוב (1000); // מבט מהיר}  

לא ראיתי השפלה כלשהי ב- RAM או ביכולתי להקצות אותו באופן דינמי בשיטה זו, אז הייתי אומר שזה כלי בר-קיימא. FWIW.

קוד הבדיקה שלך תואם את דפוס השימוש _2. הקצה רק מאגרים קצרי מועד_ שתיארתי בתשובתי הקודמת. זהו אחד מאותם דפוסי השימוש המוכרים כבטוחים.
במילים אחרות, הבעיות יעלו כשאתה מתחיל לשתף את המעבד עם קוד * לא ידוע * אחר - שזו בדיוק הבעיה שאתה חושב שאתה נמנע ממנה. באופן כללי, אם אתה רוצה משהו שתמיד יעבוד או שלא ייכשל במהלך הקישור, אתה מבצע הקצאה קבועה של הגודל המקסימלי ומשתמש בו שוב ושוב, למשל על ידי כך שהמשתמש שלך יעביר אותו אליך באתחול. זכור שאתה בדרך כלל פועל על שבב שבו ** הכל ** צריך להתאים 2048 בתים - אולי יותר בחלק מהלוחות, אבל אולי גם הרבה פחות על אחרים.
@EdgarBonet כן, בדיוק. רק רציתי לשתף.
@ChrisStratton זה בגלל אילוצי הזיכרון שאני מאמין שהקצאה דינמית של המאגר בגודל הנדרש כרגע היא אידיאלית. אם המאגר אינו נחוץ במהלך פעולה קבועה, הזיכרון זמין למשהו אחר. אבל אני מבין את מה שאתה אומר: הקצה מקום שמור כדי שמשהו אחר לא ייקח את השטח שאתה עשוי להזדקק למאגר. כל המידע והמחשבות המעולים.
הקצאה דינמית של מאגר בגודל הנדרש בלבד היא מסוכנת, כאילו כל דבר אחר מקצה לפני שתשחרר אותו אתה יכול להישאר עם פיצול - זיכרון שלא תוכל להשתמש בו מחדש. כמו כן, הקצאה דינמית כוללת תקורה למעקב. הקצאה קבועה לא אומרת שאינך יכול להכפיל את הזיכרון, זה רק אומר שאתה צריך לעבוד בשיתוף בעיצוב התוכנית שלך. עבור חיץ עם היקף מקומי בלבד, תוכל גם לשקול את השימוש בערימה. לא בדקת אם אפשרות של malloc () להיכשל.
@ChrisStratton נכון, נתח קוד זה לא בודק אם malloc () עובד או לא. ואז, נתח קוד זה משתמש בספריות ושגרות ארדואינו שמנות לשמצה. :) תודה על התובנה שלך. סביר מאוד ובעל ערך.
"זה יכול להיות מסוכן אם אתה לא מכיר את העניינים והעניין של זה, אבל זה שימושי." די מסכם את כל הפיתוח ב- C / C ++. :-)
#6
+4
Mikael Patel
2017-10-19 15:55:16 UTC
view on stackexchange narkive permalink

האם זה רעיון ממש רע להשתמש ב- malloc () ובחינם () עם Arduino?

התשובה הקצרה היא כן. להלן הסיבות לכך:

הכל על הבנה מהו MPU וכיצד לתכנת במסגרת מגבלות המשאבים הזמינים. ה- Arduino Uno משתמש ב- ATmega328p MPU עם זיכרון פלאש ISP של 32KB, 1024B EEPROM ו- SRAM של 2KB. זה לא הרבה משאבי זיכרון.

זכור כי ה- 2KB SRAM משמש לכל המשתנים הגלובליים, מילולי מחרוזות, מחסנית ושימוש אפשרי בערמה. הערימה צריכה גם להיות בעלת מקום ראש עבור ISR.

פריסת הזיכרון היא:

SRAM map

מחשבים / מחשבים ניידים של ימינו יש יותר מ פי 1,000,000 מכמות הזיכרון. שטח מחסנית ברירת מחדל של 1 מגהבייט לכל פתיל אינו נדיר אך אינו מציאותי ב- MPU.

פרויקט תוכנה משובץ צריך לבצע תקציב משאבים. זה אומדן חביון ISR, מקום זיכרון הכרחי, כוח מחשוב, מחזורי הוראה וכו '. למרבה הצער אין ארוחות צהריים בחינם ותכנות מוטבע בזמן אמת קשה הוא הקשה ביותר של כישורי התכנות. / p>

אמן לזה: "[H] ארד תכנות משובץ בזמן אמת הוא הקשה ביותר בכישורי התכנות שאפשר לשלוט בו."
האם זמן הביצוע של malloc תמיד זהה? אני יכול לדמיין שמאלוק לוקח יותר זמן כשהוא מחפש עוד בכבש הזמין אחר חריץ שמתאים? זה יהיה עוד טיעון (מלבד שנגמר איל) שלא להקצות זיכרון בדרכים?
@Paul אלגוריתמי הערימה (malloc ו- free) הם בדרך כלל לא זמן ביצוע קבוע, ואינם חוזרים על עצמם. האלגוריתם מכיל מבני חיפוש ונתונים הדורשים נעילה בעת שימוש בשרשורים (במקביל).
#7
  0
Kelly S. French
2019-05-09 21:31:11 UTC
view on stackexchange narkive permalink

אוקי, אני יודע שזו שאלה ישנה אך ככל שאני קורא יותר את התשובות כך אני חוזר שוב ושוב לתצפית שנראית בולטת.

הבעיה העצירה אמיתית

malloc () להיכשל וכן הלאה, אך עדיין זו תוצאה תקפה. השאלה שה- OP שואל נראית רק לגבי טכניקה, וכן הפרטים של הספריות בהן נעשה שימוש או ה- MPU הספציפי חשובים; השיחה פונה להפחתת הסיכון להפסקת התוכנית או לכל סיום חריג אחר. עלינו להכיר בקיומן של סביבות הסובלות את הסיכון באופן שונה בתכלית. פרויקט התחביב שלי להצגת צבעים יפים על רצועת לד לא יהרוג מישהו אם יקרה משהו חריג, אך סביר להניח ש- MCU בתוך מכונת לב-ריאה יקרה.

שלום מר טיורינג שמי הובריס

עבור רצועת הלד שלי, לא אכפת לי אם הוא יינעל, אני פשוט אאפס אותו. אם הייתי במכונת לב-ריאה הנשלטת על ידי MCU, ההשלכות של נעילה או כישלון לפעולה הן ממש חיים ומוות, אז השאלה לגבי malloc () ו- חינם () יש לפצל את בין האופן שבו התוכנית המיועדת מתמודדת עם האפשרות להפגין את הבעיה המפורסמת של מר טיורינג. יכול להיות קל לשכוח שזו הוכחה מתמטית ולשכנע את עצמנו שאם רק אנחנו חכמים מספיק אנחנו יכולים להימנע מלהיות נפגע מגבולות החישוב.

לשאלה זו צריכות להיות שתי תשובות מקובלות, האחת למי שנאלץ למצמץ כשבהייה בפניהם בבעיית העצירה ואחת לכל האחרים. בעוד שרוב השימושים בארדואינו ככל הנראה אינם יישומים קריטיים למשימה או יישום של חיים ומוות, ההבחנה עדיין קיימת ללא קשר לאיזה MPU אתה עשוי לקודד.

אני לא חושב שבעיית העצירה חלה במצב ספציפי זה בהתחשב בעובדה שהשימוש בערמה אינו בהכרח שרירותי. אם משתמשים בו בצורה מוגדרת היטב אז השימוש בערימה הופך כצפוי ל"בטוח ". הנקודה בבעיית העצירה הייתה להבין אם ניתן לקבוע מה קורה לאלגוריתם בהכרח שרירותי ולא כל כך מוגדר. זה באמת חל הרבה יותר על תכנות במובן רחב יותר וככזה אני מוצא שזה ספציפי לא מאוד רלוונטי כאן. אני אפילו לא חושב שזה רלוונטי בכלל להיות כנה לגמרי.
אני מודה בהגזמה רטורית כלשהי, אך העניין הוא באמת אם ברצונך להבטיח התנהגות, השימוש בערמה מרמז על רמת סיכון שהיא גבוהה בהרבה מהקפדה על שימוש רק בערימה.
#8
-3
JSON
2015-03-01 13:07:39 UTC
view on stackexchange narkive permalink

לא, אך יש להשתמש בהם בזהירות רבה בכל הקשור לפינוי () זיכרון שהוקצה. מעולם לא הבנתי מדוע אנשים אומרים שיש להימנע מניהול זיכרון ישיר מכיוון שזה מרמז על רמת חוסר יכולת שאינה תואמת בדרך כלל לפיתוח תוכנה.

נניח שאתה משתמש בארדואינו שלך כדי לשלוט על מזל"ט. כל שגיאה בחלק כלשהו בקוד שלך עלולה לגרום לו ליפול מהשמיים ולפגוע במישהו או משהו כזה. במילים אחרות, אם מישהו חסר את היכולות להשתמש במלק, סביר להניח שהוא לא צריך לקודד מכיוון שישנם כל כך הרבה תחומים אחרים בהם באגים קטנים יכולים לגרום לבעיות חמורות.

האם קשה יותר לאתר ולתקן את הבאגים שנגרמים על ידי malloc? כן, אבל זה יותר עניין של תסכול מצד קודנים ולא סיכון. ככל שהסיכון מגיע, כל חלק מהקוד שלך יכול להיות מסוכן באותה מידה או יותר מאשר malloc אם לא תנקוט בצעדים כדי לוודא שהוא נעשה נכון.

מעניין שהשתמשת במזל"ט כדוגמה. על פי מאמר זה (http://mil-embedded.com/articles/justifiably-apis-militaryaerospace-embedded-code/), "בשל הסיכון שלו, חלוקת זיכרון דינמית אסורה, על פי תקן DO-178B, בטיחותית. -קוד אוויוניקה משובץ קריטי. "
ל- DARPA יש היסטוריה ארוכה המאפשרת לקבלנים לפתח מפרט שמתאים לפלטפורמה שלהם - מדוע הם לא צריכים כאשר משלמי המס משלמים את החשבון. זו הסיבה שזה עולה להם 10 מיליארד דולר לפתח מה אחרים יכולים לעשות עם 10,000 דולר. כמעט נשמע כאילו אתה משתמש במתחם התעשייתי הצבאי כהפניה כנה.
הקצאה דינמית נראית כמו הזמנה לתוכנית שלך להדגים את גבולות החישוב המתוארים בבעיית העצירה. ישנן כמה סביבות שיכולות להתמודד עם כמות קטנה של סיכון להפסקה כזו וישנן סביבות (חלל, הגנה, רפואיות וכו ') שלא יסבלו כל סכנה הניתנת לשליטה, ולכן הן אינן מאפשרות פעולות ש"לא צריכות " להיכשל כי 'זה אמור לעבוד' לא מספיק טוב כשאתה משגר רקטה או שולט במכונת לב / ריאה.


שאלה ותשובה זו תורגמה אוטומטית מהשפה האנגלית.התוכן המקורי זמין ב- stackexchange, ואנו מודים לו על רישיון cc by-sa 3.0 עליו הוא מופץ.
Loading...