GammaCyber

מבוא לשפת C

Low-Level: C, Assembly ו-RE · 5–6 שעות

// סקירה · יחידה 4 — שפת C ו-Assembly

מבוא לשפת C — תחביר, טיפוסים ומערכים

ברוכים ליחידה 4! עד כה למדנו Python שמנהלת זיכרון בשבילנו. עכשיו צוללים ל-C — הבסיס לכל מערכות ההפעלה וחולשות האבטחה שנחקור.

✓נושאי השיעור
  • Python לעומת C — שפה מפורשת לעומת מקומפלת
  • מבנה תוכנית C: #include, int main(), נקודה-פסיק
  • שרשרת קימפול: gcc → קובץ בינארי
  • טיפוסים וזיכרון: char, int, unsigned, sizeof
  • Integer Overflow — מה קורה כשמספר חורג מטווחו
  • מערכים — בלוקים רציפים ונוקשים בזיכרון
  • מחרוזות ו-Null Terminator (\0) — הסוד של C
  • קלט/פלט: printf, scanf, מעצבי פורמט
  • חולשות אבטחה: Buffer Overflow דרך scanf
// פרק 1

Python לעומת C — למה C שולטת בסייבר?

ההבדל המרכזי: רמת השליטה על הזיכרון. Python = רכב אוטומטי. C = רכב ספורט ידני ללא בלמי חירום. שליטה = ביצועים + סכנה.

קריטריוןPythonC
אופן ריצהמפורשת (Interpreted) — שורה-שורהמקומפלת (Compiled) — כל הקוד לפני הריצה
ניהול זיכרוןאוטומטי — Garbage Collectorידני — המתכנת אחראי
בדיקת גבולותException בטוחלא! — Undefined Behavior / Crash
מהירותאיטית יותרמהירה מאוד — קרובה לחומרה
חולשות אבטחהמוגנות ע"י מפרשBuffer Overflow, Use-After-Free
🛡️ למה C חיונית לסייבר?

רוב מערכות ההפעלה, ה-Kernel, והתוכנות הקריטיות כתובות ב-C. רוב חולשות הזיכרון המודרניות (Buffer Overflow, Format String) נובעות מ-C. כדי לנצל חולשה — חובה להבין איך הקוד נכתב.

✏תרגול עצמי2 שאלות
שאלה 1.1
מה ההבדל העיקרי בין Python ל-C מבחינת אופן הריצה?
✓ תשובה: ד) — Python מריצה קוד דרך מפרש (Interpreter) בזמן אמת. C עוברת קומפילציה מלאה לקובץ בינארי המותאם למעבד, מה שמקנה לה מהירות רבה.
שאלה 1.2
מדוע שפת C נחשבת מסוכנת יותר מ-Python בעולם הסייבר?
✓ תשובה: ב) — ב-Python, גישה מחוץ לגבולות → IndexError בטוח. ב-C, אין בדיקה — התוכנית תמשיך לקרוא/כתוב זיכרון שלא שייך לה. תוקף חכם מנצל זאת ל-Buffer Overflow.
// פרק 2

מבנה תוכנית C ושרשרת הקימפול

C היא שפה מקומפלת — הקוד חייב לעבור תרגום מלא לפני הריצה. הקומפיילר (gcc) הוא פקיד הגירה קפדני: נקודה-פסיק אחת חסרה — הוא מסרב.

// תוכנית C בסיסית — כל חלק חיוני! #include <stdio.h> // ייבוא ספריית קלט/פלט (printf, scanf) int main() { // נקודת הכניסה — חובה בכל תוכנית printf("Welcome to Gamma Cyber!\n"); // \n = ירידת שורה return 0; // 0 = הצלחה, כל ערך אחר = שגיאה }
# קומפילציה והרצה gcc hello.c -o hello # קמפל ל-hello ./hello # הרץ # שגיאות קומפילציה נפוצות: # error: expected ';' before '}' — שכחת ; # error: 'printf' undeclared — שכחת #include <stdio.h>
💡 למה #include <stdio.h> נחוץ?
ב-C, אפילו פעולות בסיסיות כמו הדפסה לא מובנות בשפה עצמה. stdio.h (Standard I/O) מספקת את הגדרות printf ו-scanf. ללא ה-include, הקומפיילר לא מכיר את הפונקציות.
✏תרגול עצמי2 שאלות
שאלה 2.1
מה תפקיד השורה #include <stdio.h>?
✓ תשובה: ב) — ספריות הבסיס לא קיימות בשפה הטהורה. #include מייבא הצהרות פונקציות מקובץ Header כדי שהקומפיילר יכיר אותן בזמן קימפול.
שאלה 2.2
איזה סוג שגיאה יתקבל אם שכחתם נקודה-פסיק בסוף שורה?
✓ תשובה: ב) — הקומפיילר לא יצליח לתרגם קוד עם שגיאות תחביר. הוא יזרוק Compilation Error ויציין בדיוק באיזה שורה הבעיה.
// פרק 3

טיפוסים וזיכרון — קופסאות מדויקות

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

טיפוסגודלטווח (signed)שימוש
char1 byte-128 עד 127תו בודד, byte של זיכרון
unsigned char1 byte0 עד 255byte חיובי בלבד
int4 bytes-2B עד 2Bמספר שלם רגיל
unsigned int4 bytes0 עד 4Bמספר שלם חיובי
size_t8 bytes (64-bit)0 עד 18EBגדלים בזיכרון, תוצאת sizeof
#include <stdio.h> int main() { char c = 'A'; // תו בודד — גרש בודד! int n = 42; unsigned int u = 300; // sizeof — כמה bytes המשתנה תופס printf("char = %zu bytes\n", sizeof(char)); // 1 printf("int = %zu bytes\n", sizeof(int)); // 4 // הצהרת משתנה ללא אתחול = ערך זבל (Garbage Value)! int x; // x מכיל כל מה שהיה במיקום הזה בזיכרון לפני printf("x = %d\n", x); // לא מוגדר! return 0; }
! Integer Overflow — מה קורה כש-255 + 1? ›

unsigned char יכול להכיל 0–255 (8 ביטים). מה קורה אם נוסיף 1 ל-255?

unsigned char x = 255; x = x + 1; printf("%d\n", x); // מדפיס 0 !!

ה"שעון" מתאפס — חוזר ל-0. זה Integer Overflow. ב-Python: מספרים גדלים אינסוף. ב-C: חורגים מהטווח = גלישה.

חשיבות לסייבר: Integer Overflow הוא מקור לחולשות אבטחה. תוקף שיכול לגרום לגלישה עלול לשנות ערכים קריטיים.
✏תרגול עצמי3 שאלות
שאלה 3.1
כמה bytes תופס משתנה מסוג char?
✓ תשובה: ד) — לפי התקן C, sizeof(char) מחזיר תמיד 1. char הוא הגודל הקטן ביותר האפשרי — בית אחד.
שאלה 3.2
הגדרתם unsigned char x = 255; ואז x = x + 1;. מה יהיה ערך x?
✓ תשובה: ד) — הגבול העליון של byte ללא סימן הוא 255. הוספת 1 גורמת לגלישה חזרה ל-0 (Integer Overflow). זה לא שגיאה — זה ההתנהגות המוגדרת ל-unsigned types ב-C.
שאלה 3.3
מה מכיל משתנה מקומי ב-C מיד אחרי הצהרה ללא אתחול?
✓ תשובה: ג) — C לא מאתחלת משתנים מקומיים לאפס. הזיכרון מכיל מה שהיה שם לפני. שימוש בערך לא מאותחל = Undefined Behavior ובאג קשה לאיתור.
// פרק 4

מערכים ומחרוזות — הסוד של Null Terminator

מערכים ב-C הם בלוקים רציפים ונוקשים בזיכרון. מחרוזת? אין דבר כזה ב-C — זה רק מערך תווים שמסתיים ב-\0.

#include <stdio.h> int main() { // מערך מספרים — 4×4 = 16 bytes ברצף בזיכרון int numbers[4] = {10, 20, 30, 40}; // גישה: אינדקסים מ-0 עד 3 (לא 1 עד 4!) for (int i = 0; i < 4; i++) { printf("Element at index %d is: %d\n", i, numbers[i]); } // מחרוזת = מערך תווים + \0 בסוף // "Hello" = ['H','e','l','l','o','\0'] — 6 תאים! char word[6] = "Hello"; printf("%s\n", word); // מדפיס עד \0 // מלכודת: char str[5] = "Hello"; — אין מקום ל-\0! // str[5] = "Hello"; חסר בית אחד — Buffer Overflow! return 0; }
💡 האנלוגיה של הרכבת
דמיינו רכבת משא. כל קרון = תו אחד. בקרון האחרון יושב שלט "עצור" — זה \0. printf עם %s מתקדמת קרון אחר קרון עד שרואה את השלט. ללא השלט — ממשיכה עד שקורסת.
הרחבה Trace — מה יודפס? ›
char word[] = {'C','y','b','e','r','\0','A','l','p','h','a'}; printf("%s\n", word);

פלט: Cyber

printf עם %s מדפיסה עד \0. ברגע שנתקלת בו אחרי 'r' — נעצרת. "Alpha" מתעלמים ממנו לגמרי. זה ממחיש את כוח ה-Null Terminator.

✏תרגול עצמי3 שאלות
שאלה 4.1
הגדרתם char str[5] = "Hello";. מה הבעיה?
✓ תשובה: ד) — מחרוזת "Hello" = 5 אותיות + תו \0 = 6 bytes. המצב ידרוס זיכרון מחוץ למערך. התיקון: char str[6] = "Hello";
שאלה 4.2
הגדרתם מערך int arr[10];. מהו טווח האינדקסים החוקי?
✓ תשובה: ג) — אינדקסים תמיד מתחילים מ-0. מערך בגודל 10 חוקי מ-0 עד 9. גישה ל-arr[10] = Out of Bounds — Undefined Behavior!
שאלה 4.3
מה ההבדל בין 'A' לבין "A" ב-C?
✓ תשובה: ב) — גרש בודד = char (תו אחד). מרכאות = מחרוזת (מערך תווים + \0). char c = 'A'; תקין. char c = "A"; שגיאה!
// פרק 5

קלט ופלט — printf ו-scanf

printf ו-scanf עובדות עם מעצבי פורמט — סימנים שמגדירים איזה סוג נתון לפרש ולהדפיס.

מעצבסוג נתוןדוגמה
%dמספר שלם (int)printf("%d", 42) → 42
%cתו בודד (char)printf("%c", 'A') → A
%sמחרוזת (char[])printf("%s", "Hi") → Hi
%zusize_t (גודל)printf("%zu", sizeof(int)) → 4
%fמספר עשרוני (float)printf("%.2f", 3.14) → 3.14
#include <stdio.h> int main() { int age; char name[20]; // קלט — חובה & לפני שם משתנה! (כתובת הזיכרון) printf("Enter your age: "); scanf("%d", &age); // &age = כתובת הזיכרון של age printf("Enter your name: "); scanf("%s", name); // name = כבר כתובת (מערך)! // פלט printf("Hello %s, you are %d years old.\n", name, age); return 0; }
! למה & חובה ב-scanf? ›

scanf צריכה לכתוב את הנתון לתוך המשתנה. לשם כך צריכה את כתובת הזיכרון שלו — לא את הערך.

&age = "כתובת המשתנה age בזיכרון". ללא &, שולחים את הערך הנוכחי (לא מאותחל!) — תוצאה: Segmentation Fault.

חריג: למערכים (כמו name) לא צריך & — שם המערך הוא כבר הכתובת.
🔴 סכנת אבטחה: Buffer Overflow דרך scanf
char name[10]; scanf("%s", name); // תוקף מזין 20 תווים → CRASH / חולשה!

scanf לא בודקת גבולות. תוקף מזין 20 תווים → 10 תווים ראשונים ממלאים את name, 10 נוספים גולשים לזיכרון סמוך ודורסים ערכים קריטיים.

פתרון: scanf("%10s", name); — מגביל ל-10 תווים.

✏תרגול עצמי3 שאלות
שאלה 5.1
מדוע חובה להוסיף & לפני שם משתנה ב-scanf?
✓ תשובה: א) — &age שולחת ל-scanf את הכתובת בזיכרון. scanf כותבת שם את הנתון שנקלט. ללא &, שולחים ערך (מספר אקראי) ו-scanf מנסה לכתוב לאותה "כתובת" — קריסה.
שאלה 5.2
מה תדפיס printf("A\nB");?
✓ תשובה: ג) — \n הוא Escape Sequence לירידת שורה (Newline). printf מפרש אותו ומבצע ירידת שורה בין A ל-B.
שאלה 5.3 — סייבר
מדוע שימוש ב-scanf("%s", name) עלול להוות סיכון אבטחה?
✓ תשובה: ג) — scanf("%s") לא בודקת גבולות. תוקף שמזין 100 תווים לתוך char name[10] דורס זיכרון קריטי — ניתן לנצל להרצת קוד זדוני. הפתרון: scanf("%9s", name).
// פרק 6 · Hands-on

מטלות מעשיות — כתבו קוד C

5 מטלות מדורגות. לחצו על "פתרון" לאחר שניסיתם בעצמכם.

מטלה 1
שלום עולם

כתבו תוכנית C שלמה שמדפיסה Welcome to Gamma Cyber! וירידת שורה. קמפלו והריצו.

פתרון מטלה 1 ›
#include <stdio.h> int main() { printf("Welcome to Gamma Cyber!\n"); return 0; }
מטלה 2
תיקון הבאג הקלאסי

מצאו ותקנו את השגיאות בקוד הבא:

#include <stdio.h> int main() { int num printf("Enter a number: "); scanf("%d", num); printf("Double is: %d\n", num * 2); return 0; }
פתרון מטלה 2 — 2 שגיאות ›

שגיאה 1: חסרה נקודה-פסיק: int num;

שגיאה 2: חסר & ב-scanf: scanf("%d", &num);

#include <stdio.h> int main() { int num; // תוקן: נקודה-פסיק printf("Enter a number: "); scanf("%d", &num); // תוקן: & לפני num printf("Double is: %d\n", num * 2); return 0; }
מטלה 3
סכום מערך

צרו מערך מספרים שלמים בגודל 5 {10, 20, 30, 40, 50}. כתבו לולאה שסוכמת את כל הערכים ומדפיסה את הסכום.

פתרון מטלה 3 ›
#include <stdio.h> int main() { int arr[5] = {10, 20, 30, 40, 50}; int sum = 0; for (int i = 0; i < 5; i++) { sum += arr[i]; } printf("The total sum is: %d\n", sum); // 150 return 0; }
מטלה 4
סוד תו העצירה

הגדירו מחרוזת. כתבו לולאת while שעוברת אות אחר אות ומדפיסה כל אות בשורה נפרדת — אך עוצרת כשנתקלת ב-\0.

פתרון מטלה 4 ›
#include <stdio.h> int main() { char password[] = "secret"; int i = 0; while (password[i] != '\0') { printf("%c\n", password[i]); i++; } return 0; }
מטלה 5
בדיקת זוגיות

כתבו תוכנית שקולטת מספר שלם מהמשתמש ומדפיסה האם הוא זוגי או אי-זוגי. השתמשו באופרטור %.

פתרון מטלה 5 ›
#include <stdio.h> int main() { int number; printf("Enter an integer: "); scanf("%d", &number); if (number % 2 == 0) { printf("The number is Even.\n"); } else { printf("The number is Odd.\n"); } return 0; }
✏שאלות Trace — מה יודפס?2 שאלות
Trace 1
מה יודפס?
int arr[] = {2, 5, 8, 3}; int sum = 0; for (int i = 0; i < 4; i++) { if (arr[i] % 2 == 0) sum += arr[i]; } printf("Result: %d\n", sum);
✓ תשובה: א) Result: 10 — 2 זוגי (sum=2), 5 אי-זוגי, 8 זוגי (sum=10), 3 אי-זוגי. סכום הזוגיים = 2+8 = 10.
Trace 2
מה יודפס?
int i; for (i = 0; i < 3; i++) { } printf("%d", i);
✓ תשובה: א) 3 — הלולאה רצה עבור i=0,1,2. לאחר האיטרציה i=2, i מקודם ל-3, התנאי i<3 נכשל — הלולאה מסתיימת. ערך i נשאר 3 ומודפס.
// תרגול נוסף

תרגול נוסף — C: sizeof, מחרוזות ו-Overflow

שאלות העשרה נוספות לחיזוק החומר של השיעור — באותו פורמט בחירה של הקורס.

✏תרגול נוסף3 שאלות
שאלה 6.1 [קל]
מה ה-Output?
char s[] = "Cyber";
printf("%zu %zu", strlen(s), sizeof(s));
✓ תשובה נכונה: ד — char s[]="Cyber": התווים C,y,b,e,r + \0. strlen=5 (עד NULL), sizeof=6 (כולל NULL). לכן "5 6".
שאלה 6.2 [בינוני]
מה הערך של sizeof(int) * 8 ב-x86?
✓ תשובה נכונה: ג — sizeof(int)=4 בתים (x86 וגם x64). ×8 ביטים לבית = 32 ביט. שים לב: long ו-pointer כן משתנים בין x86 ל-x64.
שאלה 6.3 [קשה]
מה הבעיה בקוד?
char buf[10];
int n = atoi(argv[1]);
memset(buf, 0, n);  // מאפס n bytes
✓ תשובה נכונה: ב — buf[10] מקצה 10 בתים. n מגיע מקלט משתמש ללא בדיקה. memset(buf,0,n) עם n>10 → כתיבה מעבר לגבול = Buffer Overflow. תמיד תקף גבולות.
// אתגר מעשי

אתגר מעשי — C — תוספת: צופן קיסר

תוספת מעל 5 מטלות ה-C הקיימות (שלום עולם → זוגיות): צופן קיסר. 5 המטלות הקיימות נשמרות ומקבלות חוזה-בדיקה (קומפילציה+הרצה) בבנק ה-JSON.

T2 · בסיסי200 נק'~18 דק'build
צופן קיסר ב-C
כתבו תוכנית C שקוראת מ-stdin מחרוזת (בלי רווחים) ומספר הזזה k, ומדפיסה את הצופן הקיסרי: כל אות מוזזת ב-k מקומות במעגל (A→…→Z→A), שמירה על רישיות; תווים שאינם אותיות נשארים. הזזות שליליות נתמכות.
דוגמאות
HELLO 3 → KHOOR
xyz 3 → abc
Zebra -1 → Ydaqz
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס צופן קיסר ב-C ›
#include <stdio.h> #include <ctype.h> int main(){ char s[256]; int k; if (scanf("%255s %d", s, &k) != 2) return 1; for (int i = 0; s[i]; i++){ char c = s[i]; if (isupper((unsigned char)c)) putchar('A' + ((c-'A'+k)%26+26)%26); else if (islower((unsigned char)c)) putchar('a' + ((c-'a'+k)%26+26)%26); else putchar(c); } putchar('\n'); return 0; }
// סיכום

סיכום — שיעור 23

הנחנו את יסודות שפת C. יסודות אלו הם הבסיס להבנת Assembly, ניצול חולשות, ו-Buffer Overflow שנלמד בהמשך.

✓מה למדנו בשיעור 23
  • קומפילציה: C מקומפלת לפני ריצה — gcc hello.c -o hello
  • מבנה: #include <stdio.h> + int main() + return 0;
  • טיפוסים: char (1B), int (4B), unsigned (חיובי בלבד)
  • sizeof: בדיקת גודל משתנה בזמן קומפילציה
  • Garbage Value: משתנה לא מאותחל = ערך זבל מהזיכרון
  • Integer Overflow: unsigned char 255 + 1 = 0 — השעון מתאפס
  • מערכים: בלוקים רציפים, אינדקסים מ-0, גודל קבוע
  • Null Terminator: מחרוזת = מערך תווים + \0 — חובה!
  • printf/scanf: מעצבי פורמט %d, %s, %c, %zu
  • & ב-scanf: כתובת זיכרון — חובה לכל משתנה (לא מערך)
  • Buffer Overflow: scanf("%s") ללא הגבלה = חולשת אבטחה
🔴 שאלת ראיון גאמא — תשובה מקצועית

שאלה: "מדוע שפת C נחשבת מסוכנת בעולם הסייבר?"

תשובה מקצועית: שפת C דוגלת ב-"Trust the Programmer" — אין בדיקות גבולות אוטומטיות על מערכים, אין Garbage Collector, ואין הגנות מובנות. כשמתכנת עושה טעות בניהול זיכרון, התוכנית ניגשת לזיכרון שאינו שלה. תוקף חכם מנצל זאת לדריסת Return Address ולהרצת קוד זדוני (Buffer Overflow). ב-Python, אותה טעות הייתה מייצרת Exception בטוח.

📌 לפני שיעור 24 (פוינטרים)
  1. קמפלו והריצו את כל 5 המטלות — וודאו שאתם מבינים כל שורה
  2. נסו להזין מחרוזת ארוכה מדי ל-scanf ותראו מה קורה
  3. שיעור 24: פוינטרים — ניהול זיכרון ידני ו-Dynamic Allocation
שאלות תרגול ותשובות (10)

שאלה 1 · מה ההבדל המרכזי בין Python ל-C באופן ההרצה?

  • א. שתיהן מתקמפלות לקוד מכונה לפני ההרצה
  • ב. Python מפורשת שורה-שורה בזמן ריצה, C מתקמפלת מראש לקובץ הרצה
  • ג. C מפורשת ו-Python מתקמפלת
  • ד. ההבדל הוא רק בתחביר, לא באופן ההרצה
תשובה: ב

Python מריצה מפרש (Interpreter) על הקוד בזמן ריצה. C עוברת קימפול מראש ל-Binary של קוד מכונה — ולכן היא מהירה בהרבה אבל פחות סלחנית.

שאלה 2 · מהי שרשרת הקימפול הנכונה ב-C?

gcc main.c -o main
  • א. Preprocessor → Compiler → Assembler → Linker
  • ב. Compiler → Preprocessor → Linker → Assembler
  • ג. Linker → Compiler → Assembler
  • ד. Assembler → Linker → Preprocessor → Compiler
תשובה: א

קודם ה-Preprocessor מטפל ב-#include ו-#define, אחר כך המהדר יוצר אסמבלי, ה-Assembler הופך אותו ל-Object File, ולבסוף ה-Linker מחבר את הכול לקובץ הרצה אחד. 🎯 דגש למבחן: שגיאת "undefined reference" היא כמעט תמיד שגיאת Linker — הפונקציה הוגדרה אבל המימוש לא נמצא.

שאלה 3 · מה תפקיד השורה #include <stdio.h>?

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

stdio.h הוא Header שמכיל הצהרות (Prototypes) של פונקציות קלט/פלט. בלעדיו המהדר לא מכיר את printf ויתריע.

שאלה 4 · מה יחזיר sizeof בקוד הבא?

int arr[10];
printf("%zu", sizeof(arr));
  • א. 10
  • ב. 40 (בהנחה ש-int תופס 4 בתים)
  • ג. 4
  • ד. גודל לא ידוע — נקבע רק בזמן ריצה
תשובה: ב

sizeof מחזיר את הגודל בבתים: 10 איברים × 4 בתים = 40. 🎯 דגש למבחן: sizeof מחושב בזמן קימפול ולא בזמן ריצה. לכן על מצביע (int *p) הוא יחזיר את גודל המצביע (8 בתים ב-64 ביט) ולא את גודל המערך שאליו הוא מצביע.

שאלה 5 · כמה בתים בזיכרון תופסת המחרוזת הבאה?

char name[] = "CYBER";
  • א. 5
  • ב. 6
  • ג. 7
  • ד. תלוי במעבד
תשובה: ב

5 תווים + תו הסיום \0 (Null Terminator) = 6 בתים. ב-C כל מחרוזת מסתיימת ב-\0, וזה מה שמאפשר ל-printf לדעת איפה לעצור.

שאלה 6 · מה יקרה בקוד הבא?

char buf[4];
strcpy(buf, "HELLO");
  • א. המהדר יעצור עם שגיאה ויסרב לקמפל
  • ב. התוכנית תקצץ אוטומטית ל-4 תווים
  • ג. הכתיבה תחרוג מהמאגר ותדרוס זיכרון סמוך — Buffer Overflow
  • ד. המערכת תגדיל את buf אוטומטית
תשובה: ג

C לא בודקת גבולות. strcpy תכתוב 6 בתים לתוך מאגר של 4 ותדרוס את מה שנמצא אחריו במחסנית. 🎯 דגש למבחן — אנטומיה של קריסה: אחרי המשתנים המקומיים במחסנית שמורה כתובת החזרה (Return Address). כשהחריגה דורסת אותה, בסיום הפונקציה המעבד "קופץ" לכתובת שנדרסה — ומכאן Segmentation Fault, או במקרה של תוקף — הרצת קוד זדוני.

שאלה 7 · איזה מהמשתנים הבאים נמצא במחסנית (Stack) ואיזה בערמה (Heap)?

int a = 5;
int *p = malloc(sizeof(int) * 100);
  • א. שניהם ב-Stack
  • ב. a ב-Stack, הזיכרון ש-p מצביע עליו ב-Heap
  • ג. a ב-Heap, p ב-Stack
  • ד. שניהם ב-Heap
תשובה: ב

משתנים מקומיים יושבים במחסנית ומשתחררים אוטומטית בסיום הפונקציה. malloc מקצה ב-Heap, והאחריות לשחרר עם free היא של המתכנת — אחרת נוצרת דליפת זיכרון (Memory Leak).

שאלה 8 · מה הבעיה בקוד הבא?

int num;
scanf("%d", num);
  • א. חסר & לפני num — scanf צריכה כתובת ולא ערך
  • ב. צריך להשתמש ב-%s במקום ב-%d
  • ג. אסור לקרוא מספרים עם scanf
  • ד. אין בעיה, הקוד תקין
תשובה: א

scanf כותבת אל תוך כתובת בזיכרון, ולכן חייבים להעביר &num. בלי ה-& מועבר ערך אקראי כאילו היה כתובת — והתוכנית קורסת.

שאלה 9 · מה יודפס?

int i = 0;
while (i < 3) {
    printf("%d ", i);
    i++;
}
  • א. 0 1 2
  • ב. 1 2 3
  • ג. 0 1 2 3
  • ד. לולאה אינסופית
תשובה: א

הלולאה בודקת את התנאי לפני כל סיבוב ומדפיסה 0, 1, 2. כשi מגיע ל-3 התנאי נכשל והלולאה נעצרת.

שאלה 10 · מדוע C נחשבת מסוכנת יותר מ-Python בעולם הסייבר?

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

ב-C המתכנת אחראי לזיכרון: אין בדיקת גבולות, אין איסוף זבל ואין הגנה מובנית. רוב חולשות ה-Memory Corruption ההיסטוריות נולדו בדיוק מכאן.