3.3 פוינטרים פתרון
📘 פתרון תרגול – פוינטרים, מחרוזות, מערכים ו־scanf¶
1. קלט מספר שלם מהמשתמש¶
#include <stdio.h>
int main() {
int a;
printf("הכנס מספר:\n");
scanf("%d", &a);
printf("המספר שהכנסת הוא: %d\n", a);
return 0;
}
2. קלט אות מהמשתמש, הדפסת ערך ה־ASCII שלה¶
#include <stdio.h>
int main() {
char c;
printf("הכנס אות:\n");
scanf(" %c", &c); // שים לב לרווח לפני %c כדי לדלג על תווי whitespace
printf("ערך ה־ASCII של %c הוא: %d\n", c, c);
return 0;
}
3. הדפסת מחרוזת וכתובת בזיכרון¶
#include <stdio.h>
int main() {
char* str = "hello";
printf("המחרוזת: %s\n", str);
printf("הכתובת בזיכרון: %p\n", str);
return 0;
}
4. קלט של שלושה מספרים לתוך מערך¶
#include <stdio.h>
int main() {
int arr[3];
printf("הכנס שלושה מספרים:\n");
scanf("%d", &arr[0]);
scanf("%d", &arr[1]);
scanf("%d", &arr[2]);
printf("המספרים הם: %d, %d, %d\n", arr[0], arr[1], arr[2]);
return 0;
}
5. שימוש בפוינטר לגישה למערך¶
#include <stdio.h>
int main() {
int arr[3] = {10, 20, 30};
int* p = arr;
printf("arr[0] = %d, *(p+0) = %d\n", arr[0], *(p + 0));
printf("arr[1] = %d, *(p+1) = %d\n", arr[1], *(p + 1));
printf("arr[2] = %d, *(p+2) = %d\n", arr[2], *(p + 2));
return 0;
}
6. קלט מחרוזת מהמשתמש¶
#include <stdio.h>
int main() {
char name[100];
printf("הכנס מילה (מחרוזת ללא רווחים):\n");
scanf("%s", name);
printf("הכנסת את המחרוזת: %s\n", name);
return 0;
}
7. הדפסת argc ו־argv¶
#include <stdio.h>
int main(int argc, char** argv) {
printf("כמות הארגומנטים: %d\n", argc);
printf("שם התוכנה: %s\n", argv[0]);
if (argc > 1) {
printf("הארגומנט הראשון: %s\n", argv[1]);
}
return 0;
}
8. הדפסת התו הראשון, השני והשלישי של הארגומנט הראשון¶
#include <stdio.h>
int main(int argc, char** argv) {
if (argc > 1) {
char* p = argv[1];
printf("תו ראשון: %c\n", p[0]);
printf("תו שני: %c\n", p[1]);
printf("תו שלישי: %c\n", p[2]);
} else {
printf("לא הוזן ארגומנט ראשון\n");
}
return 0;
}
💡 תרגיל בונוס – שימוש ב־scanf עם כמה סוגים¶
#include <stdio.h>
int main() {
char name[100];
int age;
int fav;
printf("הכנס שם:\n");
scanf("%s", name);
printf("הכנס גיל:\n");
scanf("%d", &age);
printf("הכנס מספר אהוב:\n");
scanf("%d", &fav);
printf("שלום %s, אתה בן %d והמספר האהוב עליך הוא %d\n", name, age, fav);
return 0;
}
ניקח את הקוד הבא:
#include <stdio.h>
int main() {
char name[100];
int age;
int fav;
printf("הכנס שם:\n");
scanf("%s", name);
printf("הכנס גיל:\n");
scanf("%d", &age);
printf("הכנס מספר אהוב:\n");
scanf("%d", &fav);
printf("שלום %s, אתה בן %d והמספר האהוב עליך הוא %d\n", name, age, fav);
return 0;
}
נניח שקימפלנו עם:
שימו לב ש-g הוא דגל debug בgcc, ונותן לנו עוד מידע בקובץ האובייקט שיכול לשמש אותנו שאנחנו חוקרים את התוכנה עם objdump.
ונשתמש ב־objdump -d bonus > bonus.asm כדי לראות את קוד האסמבלי שנוצר.
חלק מהאסמבלי שיתקבל (מפושט לצורך הסבר):¶
push ebp
mov ebp, esp
sub esp, 0x70 ; מקצה מקום ל־name, age, fav (100 בתים + 4 + 4)
lea eax, [ebp-0x64] ; כתובת name
push eax
push offset .LC0 ; פורמט "%s"
call scanf
lea eax, [ebp-0x4] ; כתובת age
push eax
push offset .LC1 ; פורמט "%d"
call scanf
lea eax, [ebp-0x8] ; כתובת fav
push eax
push offset .LC2 ; פורמט "%d"
call scanf
lea eax, [ebp-0x64] ; name
mov edx, [ebp-0x4] ; age
mov ecx, [ebp-0x8] ; fav
push ecx
push edx
push eax
push offset .LC3 ; פורמט "שלום %s, אתה בן %d..."
call printf
💡 הסבר שלב-שלב:¶
-
sub esp, 0x70: מקצה מקום ל־char name[100]ול־2 משתנים בגודל 4 בתים כל אחד (סה"כ 112 בתים = 0x70). -
כל
lea eax, [ebp-offset]– מחשבת את הכתובת של משתנה מקומי בזיכרון. -
push offset .LCx– שולח לפונקציה את מחרוזת הפורמט (כמו "%s", "%d"...). -
push eax– שולח את כתובת המשתנה לפונקציה. -
call scanf/call printf– קריאה לפונקציית ספרייה שמוגדרת בלינקר. -
ההדפסה הסופית עושה שימוש ב־
pushעבור כל משתנה לפי הסדר ש־printfדורש.
סיכום:¶
-
הקוד ב־C מקצה משתנים בזיכרון ה־Stack.
-
כל קריאה ל־
scanfאוprintfמתבצעת על ידי שימוש ב־pushשל פרמטרים ו־call. -
הכתובות לזיכרון מקומי מחושבות יחסית ל־
ebp– כך המעבד ניגש למשתנים בפונקציה. -
הקוד שמתקבל תואם בצורה מלאה את ההרצאות הקודמות על Stack, קריאות לפונקציה, שימוש ב־lea וכו'.