4 טעויות תכנות C נפוצות - וחמישה טיפים להימנע מהן

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

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

טעות C נפוצה: לא לשחרר mallocזיכרון (או לשחרר אותו יותר מפעם אחת)

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

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

טעות C נפוצה: קריאת מערך מחוץ לתחום

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

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

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

טעות C נפוצה: לא לבדוק את התוצאות של malloc

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

למרות שלמחשבים יש כיום ג'יגה-בייט של זיכרון RAM לזרוק, עדיין תמיד הסיכוי mallocיכול להיכשל, במיוחד בלחץ זיכרון גבוה או בעת הקצאת לוחות זיכרון גדולים בבת אחת. הדבר נכון במיוחד עבור תוכניות C אשר "מקצות" לוח זיכרון גדול ממערכת ההפעלה תחילה ואז מחלקות אותו לשימושן האישי. אם ההקצאה הראשונה הזו נכשלת מכיוון שהיא גדולה מדי, ייתכן שתוכל ללכוד את הסירוב הזה, להקטין את ההקצאה ולכוונן את היוריסטיקות השימוש בזיכרון של התוכנית בהתאם. אבל אם הקצאת הזיכרון לא תושלם, התוכנית כולה עשויה להשתבש.

טעות C נפוצה: שימוש void*עבור מצביעים גנריים לזיכרון

השימוש  void* בכדי להצביע על זיכרון הוא הרגל ישן - והרעה רעה. מצביע לזיכרון צריך להיות תמיד char*, unsigned char*או  uintptr_t*. סוויטות מהדר מודרניות C צריכות לספק uintptr_tכחלק מ- stdint.h

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

הימנעות מטעויות C נפוצות - 5 טיפים

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

בנה תוכניות C כך שהבעלות על הזיכרון תישאר ברורה

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

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

השתמש באפשרויות מהדר C השומרות מפני בעיות זיכרון

ניתן לסמן רבות מהבעיות המתוארות במחצית הראשונה של מאמר זה באמצעות אפשרויות מהדר קפדניות. מהדורות אחרונות של gcc, למשל, מספקות כלים כמו AddressSanitizer ("ASAN") כאפשרות אוסף לבדיקת טעויות נפוצות של ניהול זיכרון.

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

השתמש ב- Cppcheck או Valgrind לניתוח קוד C עבור דליפות זיכרון

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

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

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

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

הפוך אוטומטית לניהול זיכרון C באמצעות אספן אשפה

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

כן, זה אפשרי ב- C. אתה יכול להשתמש במשהו כמו אספן האשפה Boehm-Demers-Weiser כדי להוסיף ניהול זיכרון אוטומטי לתוכניות C. עבור תוכניות מסוימות, השימוש באספן Boehm יכול אפילו להאיץ את העניינים. זה יכול לשמש אפילו כמנגנון גילוי נזילות.

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

אל תשתמש ב- C כאשר שפה אחרת תעשה זאת

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

אם יש לך פרוייקט שבו ביצועי הביצוע יוגבלו בעיקר על ידי קלט / פלט או גישה לדיסק, כתיבתו ב- C לא עשויה להפוך את זה למהיר יותר בדרכים החשובות, וכנראה רק יהפוך אותו ליותר טעוי וקשה לְתַחְזֵק. אותה תוכנית בהחלט יכולה להיכתב ב- Go או ב- Python.

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