9.1 שלבי הקומפילציה פתרון
פתרונות¶
פתרון 1¶
- לאחר הרצת
gcc -E stages.c -o stages.i:
נקבל בסביבות 700-800 שורות (תלוי בגרסת gcc ובמערכת). כל השורות האלה הן תוכן ה-header stdio.h וכל הקבצים שהוא עושה להם include. הקוד שלנו הוא רק 4-5 שורות בסוף.
- כשנחפש את
mainבתוךstages.i, נראה:
המקרואים NAME ו-YEAR הוחלפו בערכים שלהם - "Student" ו-2026. זו החלפה טקסטואלית שהpreprocessor ביצע.
- בקובץ
stages.s, המחרוזות נמצאות בסקשן.rodata(read-only data):
- הסקשנים ב-
stages.o:
נראה סקשנים כמו: .text, .data, .bss, .rodata, .comment, .note.GNU-stack, .symtab, .strtab, .shstrtab, .rela.text.
- הסמלים שצריכים relocation:
נראה שהסמל printf (או puts אם הקומפיילר החליט לבצע אופטימיזציה) צריך relocation - כי ההגדרה שלו נמצאת ב-libc, לא בקובץ שלנו.
פתרון 2¶
- לאחר
gcc -E preproc.c, השורהLOG("Debug mode is ON")הורחבה ל:
כי __FILE__ הוחלף בשם הקובץ, __LINE__ הוחלף במספר השורה (8), וההודעה נשארה כמו שהיא. כל המקרואים הורחבו באופן רקורסיבי.
-
ללא
#define DEBUG, כל הקוד בין#ifdef DEBUGל-#endifנעלם לחלוטין מהפלט. הpreprocessor הסיר אותו. בפלט שלgcc -Eהשורות האלה פשוט לא יופיעו. -
עם DEBUG:
בלי DEBUG:
שורות הדיבוג לא מופיעות כי הקוד שלהן הוסר בשלב הpreprocessing.
- שימוש ב-
gcc -DDEBUGמאפשר להגדיר מקרואים מבחוץ, בלי לשנות את קוד המקור. היתרון: אפשר להחליט בזמן build אם להפעיל דיבוג או לא, בלי לערוך קבצים. ב-Makefile זה נראה כך:
פתרון 3¶
-
קימפול:
-
פלט של
nm utils.o:
0000000000000000 T increment
0000000000000000 t internal_helper
0000000000000000 D global_counter
0000000000000004 d local_counter
- T (אות גדולה) = סמל גלובלי מוגדר בסקשן
.text- נגיש מקבצים אחרים - t (אות קטנה) = סמל מקומי (static) בסקשן
.text- נגיש רק בתוך הקובץ - D (אות גדולה) = סמל גלובלי מוגדר בסקשן
.data - d (אות קטנה) = סמל מקומי (static) בסקשן
.data
הפונקציה internal_helper והמשתנה local_counter הם static, ולכן סמלים מקומיים (אות קטנה). הלינקר לא יחשוף אותם לקבצים אחרים.
- פלט של
nm app.o:
הסמלים מסוג U (undefined) הם: global_counter, increment, ו-printf. הם undefined כי הקובץ app.c משתמש בהם אבל לא מגדיר אותם - ההגדרה שלהם נמצאת בקבצים אחרים (utils.o ו-libc).
- הלינקוג' מצליח:
הלינקר מוצא את increment ו-global_counter ב-utils.o, ואת printf ב-libc.
- אם ננסה לגשת ל-
local_counterמתוךapp.c:
נקבל שגיאת לינקר:
כי local_counter הוגדר כ-static - הוא סמל מקומי (t/d) ולא נגיש מקבצים אחרים. הלינקר פשוט לא רואה אותו.
פתרון 4¶
-
ללא אזהרות, הקוד מתקמפל (gcc מקמפל כברירת מחדל בלי אזהרות רבות). ייתכן שתופיע אזהרה על הגישה מחוץ לגבולות המערך, תלוי בגרסת gcc.
-
עם
-Wallנקבל אזהרות כמו:
warnings.c: In function 'calculate':
warning: 'result' may be used uninitialized [-Wmaybe-uninitialized]
warnings.c: In function 'main':
warning: implicit conversion from 'double' to 'int' [-Wfloat-conversion]
warning: format '%d' expects argument, but nothing provided [-Wformat]
-
עם
-Wall -Wextraייתכנו אזהרות נוספות על פרמטרים לא מנוצלים או בדיקות נוספות. -
עם
-Werrorהקימפול נכשל - כל אזהרה הופכת לשגיאה. לא ניתן ליצור את קובץ ההרצה עד שנתקן את כל הבעיות. -
קוד מתוקן:
#include <stdio.h>
int calculate(int a, int b) {
int result = 0; // אתחול ברירת מחדל
if (a > 0)
result = a + b;
return result;
}
int main() {
int x = 3; // הסרת המספר העשרוני
int arr[5];
arr[4] = 42; // אינדקס חוקי
printf("Result: %d\n", calculate(x, 2));
printf("Value: %d\n", arr[4]); // הוספת ארגומנט
return 0;
}
פתרון 5¶
-
הקימפול בארבע רמות מייצר ארבעה קבצי אסמבלי.
-
הפונקציה
dead_codeב--O0:
dead_code:
pushq %rbp
movq %rsp, %rbp
movl $7, -4(%rbp) # x = 3 + 4 = 7 (קיפול קבועים גם ב-O0)
movl -4(%rbp), %eax
addl %eax, %eax # y = x * 2
movl %eax, -8(%rbp)
movl $42, %eax # return 42
popq %rbp
ret
ב--O2:
הקומפיילר ב-O2 זיהה ש-x ו-y הם dead code (התוצאה שלהם לא משמשת לכלום) והסיר אותם לחלוטין. נשאר רק return 42.
-
ב-
mainב--O2, הקומפיילר מבצע constant folding:10 + 20מחושב בזמן קומפילציה ל-30, ו-30 * 2ל-60. באסמבלי נראה ישירות את הערך 60 (או שהקומפיילר יסיר גם אתbאם הוא לא בשימוש). -
ב-
-O3, הקומפיילר עלול לבצע כמה דברים מתקדמים עם הלולאה ב-compute: - לזהות שהתוצאה של
sum += i * 2 + 1היא בעצםn * n(סכום סדרה חשבונית) ולהחליף את כל הלולאה בנוסחה - או לבצע פריסת לולאה (loop unrolling) - לבצע כמה איטרציות בכל סיבוב
- או לבצע וקטוריזציה עם הוראות SIMD
בהשוואה ל--O0 שבו הלולאה קיימת כפי שנכתבה, ב--O3 יש סיכוי טוב שהלולאה תיעלם לחלוטין ותוחלף בחישוב ישיר.
- השוואת גודל:
פלט לדוגמה:
שימו לב ש-O3 עלול להיות גדול מ-O2 - כי אופטימיזציות אגרסיביות כמו loop unrolling מגדילות את כמות הקוד (אבל הקוד רץ מהר יותר). O2 נחשב לאיזון הטוב ביותר בין מהירות לגודל.