לדלג לתוכן

10.6 - varargs מתחת למכסה המנוע - תרגול

התרגילים בשיעור הזה מניחים סביבת x86-64 על לינוקס. אם אתם עובדים על מכונת arm64 (למשל מק עם Apple Silicon), הריצו אותם בתוך מכונה וירטואלית או קונטיינר של x86-64, אחרת המבנה של va_list יהיה שונה לגמרי.

תרגיל 1 - חיזוי מיקום הארגומנטים

נתונה הפונקציה והקריאה הבאות:

void report(const char *tag, int count, ...);

report("stats", 3, 42, 2.5, "done", 7);

ענו על השאלות הבאות על הנייר, לפני שאתם מריצים משהו:

  1. באיזה רגיסטר יעבור כל אחד מששת הארגומנטים?
  2. מה יהיה הערך של al בקריאה הזו?
  3. מה יהיה הערך ההתחלתי של gp_offset ושל fp_offset אחרי va_start(ap, count)?
  4. אחרי שקראנו va_arg(ap, int) פעם אחת, מה יהיה gp_offset?

אחר כך אמתו את עצמכם: כתבו את הקוד, קמפלו עם gcc -O0 -S, ובדקו את הפרולוג ואת האתחול של ap.

תרגיל 2 - להסתכל על va_list מבפנים

כתוב תוכנית שמדפיסה את השדות של va_list לפני ואחרי כל קריאה ל-va_arg, כדי לראות במו עיניכם את המונים מתקדמים.

הגדירו בעצמכם מבנה שתואם ל-va_list של x86-64, ועשו cast אליו:

typedef struct {
    unsigned int gp_offset;
    unsigned int fp_offset;
    void *overflow_arg_area;
    void *reg_save_area;
} my_va_list;

הפונקציה שלכם צריכה לקבל מספר משתנה של ארגומנטים שלמים, ולהדפיס אחרי כל va_arg את gp_offset הנוכחי.

דוגמה להרצה:

initial:  gp_offset=8   fp_offset=48
after #1: gp_offset=16  value=10
after #2: gp_offset=24  value=20
after #3: gp_offset=32  value=30

תרגיל 3 - למה צריך va_copy

כתוב פונקציה void print_normalized(int count, ...) שמקבלת count מספרים שלמים ומדפיסה כל אחד מהם כאחוז מהמקסימום שברשימה.

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

נסו קודם לעבור פעמיים על אותו va_list בלי va_copy, וראו מה קורה. אחר כך תקנו עם va_copy.

דוגמה להרצה:

print_normalized(4, 25, 50, 100, 75);
max = 100
25 -> 25%
50 -> 50%
100 -> 100%
75 -> 75%