לדלג לתוכן

3.2 הבסיס פתרון

✅ פתרון תרגול 1 – פונקציית main והחזרת ערך

#include <stdio.h>

int main(int argc, char** argv, char** envp) {
    printf("argc = %d\n", argc);
    return -1;
}

הסבר: הקרנל ישמור את קוד ההחזרה (return code) של התוכנה. -1 יכול להעיד על כישלון בתוכנית. argc שומר את מספר הארגומנטים שהועברו לתוכנית.


✅ פתרון תרגול 2 – הגדרת משתנים והצבת ערכים

#include <stdio.h>
#include <stdbool.h>

int main() {
    int i = 42;
    char c = 'A';
    short s = 32000;
    float f = 3.14f;
    double d = 2.71828;
    bool b = true;
    char* str = "hello";
    char hex = 0xFA;

    printf("int: %d\n", i);
    printf("char: %c (ASCII: %d)\n", c, c);
    printf("short: %d\n", s);
    printf("float: %f\n", f);
    printf("double: %lf\n", d);
    printf("bool: %d\n", b);
    printf("string: %s\n", str);
    printf("hex: %x\n", hex);

    return 0;
}

✅ פתרון תרגול 3 – פעולות מתמטיות

#include <stdio.h>

int main() {
    int a = 7, b = 3;
    float d;

    printf("a + b = %d\n", a + b);
    printf("a - b = %d\n", a - b);
    printf("a * b = %d\n", a * b);
    d = a / b;
    printf("a / b (int) = %f\n", d); // עדיין int חלקי int -> תוצאה שלמה

    d = (float)a / b;
    printf("a / b (float) = %f\n", d); // תוצאה עשרונית

    printf("a %% b = %d\n", a % b);

    return 0;
}

✅ פתרון תרגול 4 – ++ ו־--

#include <stdio.h>

int main() {
    int a = 5;
    int b = a++;
    printf("a: %d, b: %d\n", a, b); // a=6, b=5

    int c = ++a;
    printf("a: %d, c: %d\n", a, c); // a=7, c=7

    return 0;
}

✅ פתרון תרגול 5 – פעולות לוגיות ובינאריות

#include <stdio.h>

int main() {
    char a = 0b10101010;
    char b = 0b11110000;
    char c;

    c = a & b;
    printf("a & b = %x\n", c);

    c = a | b;
    printf("a | b = %x\n", c);

    c = a ^ b;
    printf("a ^ b = %x\n", c);

    c = !a;
    printf("!a = %d\n", c); // לוגי, 0 או 1

    printf("!(a&b) = %d\n", !(a & b));
    printf("!(a|b) = %d\n", !(a | b));
    printf("!(a^b) = %d\n", !(a ^ b));

    return 0;
}

✅ פתרון תרגול 6 – מחרוזות וכתובות

#include <stdio.h>

int main() {
    char* msg = "hello world";
    printf("msg: %s\n", msg);
    printf("msg address: %p\n", msg);

    char ch = 'a';
    printf("char: %c, ascii: %d\n", ch, ch);

    return 0;
}

✅ פתרון תרגול 7 – שילוב הדפסות

#include <stdio.h>

int main() {
    char* name = "שיר";
    char* city = "תל אביב";
    int age = 21;

    printf("שלום, קוראים לי %s, אני גרה ב-%s ואני בת %d\n", name, city, age);

    return 0;
}

נקמפל את הקובץ עם שמירת קובץ אובייקט והרצת disassembler:

gcc -m64 -c main.c -o main.o
objdump -d main.o

חלק מהפלט שתראו (פשטתי אותו לצורך ההסבר):

0000000000001139 <main>:
    1139:   55                      push   %rbp
    113a:   48 89 e5                mov    %rsp,%rbp
    113d:   48 8d 3d xx xx xx xx    lea    name_str(%rip),%rdi
    1144:   48 8d 35 yy yy yy yy    lea    city_str(%rip),%rsi
    114b:   ba 15 00 00 00          mov    $0x15,%edx       ; age = 21
    1150:   b8 00 00 00 00          mov    $0x0,%eax
    1155:   e8 zz zz zz zz          call   printf
    115a:   b8 00 00 00 00          mov    $0x0,%eax
    115f:   5d                      pop    %rbp
    1160:   c3                      ret

הסבר מפורט על כל שורה

כתובת פקודה הסבר
push %rbp שמירת בסיס המחסנית הקודם
mov %rsp, %rbp יצירת בסיס חדש למחסנית (prologue של פונקציה)
lea name_str(%rip), %rdi טען את הכתובת של המחרוזת name לפרמטר הראשון של printf
lea city_str(%rip), %rsi טען את הכתובת של city לפרמטר השני
mov $21, %edx העבר את הגיל לפרמטר השלישי
mov $0, %eax ניקוי register לפני קריאה לפונקציה לקונבנצית קריאה שלינוקס
call printf קריאה לפונקציה printf
mov $0, %eax החזרת 0 כקוד חזרה
pop %rbp שחזור ערך בסיס המחסנית הקודם
ret יציאה מהפונקציה

הערות חשובות

  • הפרמטרים לפונקציה printf מועברים לפי ABI (הקונבנציה בלינוקס) כך:
  • %rdi → הפרמטר הראשון (format string)
  • %rsi, %rdx, %rcx... → פרמטרים נוספים

  • ההוראות lea משמשות לטעינת כתובות למחרוזות.

  • הפונקציה printf עצמה מוגדרת בקובצי ספריה חיצוניים (בlibc) – ולכן לא תראו את מימושה ב־objdump על הקובץ שלכם, אלא רק אחרי הלינקר (לא בקובץ האובייקט).