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:
חלק מהפלט שתראו (פשטתי אותו לצורך ההסבר):
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על הקובץ שלכם, אלא רק אחרי הלינקר (לא בקובץ האובייקט).