GammaCyber

Structs, Unions, Bitfields ו-Bitwise — הנושא המלא

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

// סקירה

Structs, Unions ו-Bitwise — C ברמת הזיכרון

שיעור זה מדבר ב-C כשפת מכונה — איך מבנים יושבים בזיכרון, איך פועלים על ביטים, ואיך זה קשור לניתוח פרוטוקולי רשת ושאלות המיון.

✓נושאי השיעור
  • Struct — הגדרה, ייצוג בזיכרון, Padding ו-Alignment
  • typedef — קיצורי דרך לטיפוסים מורכבים
  • Struct Pointers ו-Arrow Operator (->)
  • Nested Structs — Struct בתוך Struct
  • Union — שדות שחולקים את אותו מקום בזיכרון
  • Union לניתוח פרוטוקולי רשת (TCP Header, IP Address)
  • Bitwise Operators — AND, OR, XOR, NOT, Shift
  • Bitmask Idioms — Set/Clear/Toggle/Check
  • Bit Fields — שדות בגודל ביטים ב-Struct
  • Endianness — Little vs Big Endian
// פרק 1

Struct — מבנה נתונים מורכב

ב-Python יש Class. ב-C יש Struct — מבנה שאוגד שדות של טיפוסים שונים תחת שם אחד. typedef חוסך את כתיבת struct בכל פעם.

הגדרה ושימוש בסיסי

// הגדרת Struct — תבנית בלבד, לא תופסת זיכרון struct Point { int x; // 4 bytes int y; // 4 bytes }; // סה"כ: 8 bytes // יצירת משתנה + גישה עם Dot Operator struct Point p1; p1.x = 10; p1.y = 20; // אתחול עם שמות שדות (מומלץ!) struct Point p2 = {.x = 5, .y = 15}; printf("Point: (%d, %d)\n", p2.x, p2.y); // Point: (5, 15) // typedef — קיצור שם typedef struct { int x; int y; } Point; // עכשיו אפשר: Point p3 = {3, 7}; // במקום: struct Point p3 = {3, 7};

Struct Pointers ו-Arrow Operator (->)

typedef struct { char name[50]; int age; } Person; Person p = {"Bob", 25}; Person *ptr = &p; // שתי שיטות שקולות לגישה דרך מצביע: printf("%s\n", (*ptr).name); // שיטה 1 — מסורבלת printf("%s\n", ptr->name); // שיטה 2 — Arrow Operator ← מועדפת! ptr->age = 26; // שינוי ערך דרך מצביע // פונקציה שמקבלת מצביע ל-Struct ומשנה אותו void birthday(Person *p) { p->age++; // משנה את ה-Struct המקורי! }
✏תרגול — Struct ו-typedef3 שאלות
שאלה 1.1
מה שקול לחלוטין לביטוי ptr->field?
✓ תשובה: ג) (*ptr).field — Arrow Operator הוא קיצור: קודם דרפרנס את המצביע (*ptr), אחר כך גש לשדה (.field). ⚠️ *ptr.field שגוי — קדימות: .field רץ לפני * ונותן שגיאה.
שאלה 1.2
מהי השגיאה בקוד הבא?
struct Node n; n->id = 5;
✓ תשובה: א. n הוא משתנה מסוג struct (קופסה פיזית), לא מצביע. Arrow Operator (->) משמש רק עם מצביעים לStruct. הנכון: n.id = 5;
שאלה 1.3
מה קורה כאשר מבצעים השמה של Struct אחד לאחר: Point p2 = p1;?
✓ תשובה: א. בשפת C, השמה של Struct מבצעת העתקת זיכרון מלאה (Shallow Copy). שינוי ב-p2 לא ישפיע על p1 ולהפך — הם עצמאיים.
// פרק 2

Struct בזיכרון — Padding ו-Alignment

הקומפיילר לא מניח שדות צמודים. הוא מיישר (Aligns) כל שדה לכתובת שמתחלקת בגודלו — ומוסיף Padding (bytes ריקים) כדי לעמוד בדרישה.

// דוגמה: Struct עם Padding typedef struct { char a; // 1 byte → כתובת 0 // Padding: 3 bytes (כדי ש-b יתחיל בכתובת 4) int b; // 4 bytes → כתובת 4 char c; // 1 byte → כתובת 8 // Padding: 3 bytes (כדי שגודל הStruct יתחלק ב-4) // סה"כ: 12 bytes (לא 6!) } Padded; printf("sizeof(Padded) = %zu\n", sizeof(Padded)); // → 12 // בדיקת כתובות שדות עם offsetof #include <stddef.h> printf("offset of a: %zu\n", offsetof(Padded, a)); // 0 printf("offset of b: %zu\n", offsetof(Padded, b)); // 4 printf("offset of c: %zu\n", offsetof(Padded, c)); // 8
כתובתתוכןהסבר
0x00a (char)שדה a — 1 byte
0x01–0x03PAD PAD PADPadding — 3 bytes ריקים
0x04–0x07b b b b (int)שדה b — 4 bytes, Aligned ל-4
0x08c (char)שדה c — 1 byte
0x09–0x0BPAD PAD PADPadding סיום — גודל Struct מתחלק ב-4
// עצה: סדר שדות חכם = פחות Padding! typedef struct { int b; // 4 bytes → כתובת 0 char a; // 1 byte → כתובת 4 char c; // 1 byte → כתובת 5 // Padding: 2 bytes // סה"כ: 8 bytes במקום 12! } Compact; // כלל אצבע: סדר שדות מהגדול לקטן → Padding מינימלי
🔑 חשוב לגאמא — חישוב sizeof
כלל Alignment: כל שדה מיושר לכתובת שמתחלקת בגודלו (char=1, short=2, int=4, double=8).
גודל כולל: מתחלק בגודל השדה הגדול ביותר.
שאלות מיון: כתבו שדות לפי סדר + הוסיפו Padding לפי הכלל.
✏תרגול — Padding ו-Alignment3 שאלות
שאלה 2.1
מדוע sizeof(struct { char a; int b; }) מחזיר 8 ולא 5?
✓ תשובה: ד. char=1 byte, אז Padding 3 bytes, אז int=4 bytes. סה"כ: 1+3+4 = 8. מעבדים מודרניים קוראים מהר יותר מכתובות "מיושרות".
שאלה 2.2
מהו sizeof של הstruct הבא?
typedef struct { char a; double b; int c; char d; } MyStruct;
✓ תשובה: ג) 24 bytes. char a → כתובת 0 (1B) + Padding 7B → double b → כתובת 8 (8B) → int c → כתובת 16 (4B) → char d → כתובת 20 (1B) + Padding 3B → גודל כולל מתחלק ב-8 (double) = 24B.
שאלה 2.3
כיצד ניתן לאלץ את הקומפיילר לא להוסיף Padding (לארוז צמוד)?
✓ תשובה: ג. Packed Struct = ללא Padding. ⚠️ חסרון: ביצועים איטיים יותר + בעיות Alignment בחומרה מסוימת. שימוש: שליחת Structs ברשת בלי bytes ריקים מיותרים.
// פרק 3

Union — שדות שחולקים זיכרון

Union דומה ל-Struct — אבל כל שדות חולקים את אותו מקום בזיכרון. גודל = גדול ביותר. שימושי לניתוח פרוטוקולים וType Punning.

typedef union { int i; // 4 bytes float f; // 4 bytes char bytes[4]; // 4 bytes } MultiView; // כל השדות מפנים לאותו מקום בזיכרון! MultiView u; u.i = 65; // 0x00000041 printf("As int: %d\n", u.i); // 65 printf("As char: %c\n", u.bytes[0]); // 'A' (ASCII 65) // כתיבה לשדה אחד "משנה" את כל השאר! u.f = 3.14; printf("After f=3.14, i = %d\n", u.i); // ערך int של pattern 3.14 (לא 3!)

Union בפרוטוקולי רשת

// IP Address — נגיש גם כ-uint32_t וגם כ-4 Bytes נפרדים typedef union { uint32_t addr; // הכתובת כמספר שלם uint8_t octets[4]; // 4 Octets נפרדים } IPAddress; IPAddress ip; ip.addr = 0xC0A80001; // 192.168.0.1 // גישה לכל Octet בנפרד: printf("IP: %d.%d.%d.%d\n", ip.octets[3], ip.octets[2], ip.octets[1], ip.octets[0]); // 192.168.0.1
הרחבה Tagged Union — ייצוג Variant Data ›
// JSON value — יכול להיות מספר, מחרוזת, או bool typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } ValueType; typedef struct { ValueType type; // "tag" — איזה שדה בשימוש union { int ival; float fval; char sval[64]; } data; } Value; Value v1 = {.type = TYPE_INT, .data = {.ival = 42}}; Value v2 = {.type = TYPE_STRING, .data = {.sval = "hello"}}; void printValue(Value *v) { switch (v->type) { case TYPE_INT: printf("int: %d\n", v->data.ival); break; case TYPE_STRING: printf("str: %s\n", v->data.sval); break; } }
✏תרגול — Union3 שאלות
שאלה 3.1
הגדרתם union { char a[4]; int b; } u;. כתבתם a[0]='A'; a[1]='B'; ואז קראתם b. מה קרה?
✓ תשובה: ב. ב-Union, כל השדות שותפים לאותו זיכרון. כתיבה ל-a[0] ו-a[1] ממלאת את bytes[0] ו-bytes[1] של ה-int. קריאת b מחזירה את הפרשנות של הביטים האלה כ-int שלם.
שאלה 3.2
מה ההבדל המרכזי בין struct ל-union מבחינת זיכרון?
✓ תשובה: א. struct = סכום גדלי כל השדות (+ Padding). union = גודל השדה הגדול ביותר. כתיבה לשדה אחד ב-union "דורסת" את שאר השדות.
שאלה 3.3
כיצד ניתן לבדוק Endianness של המחשב באמצעות union?
✓ תשובה: א. מציבים 1 ב-n. ב-Little Endian: הbyte הנמוך (0x01) נשמר בכתובת הנמוכה → b[0]=1. ב-Big Endian: הbyte הגבוה ראשון → b[0]=0, b[3]=1.
// פרק 4

Bitwise Operators — מניפולציה ברמת הביט

פעולות Bitwise פועלות ישירות על הביטים של מספר. כל TCP Header, כל IP Packet, כל Flags — מבוסס על מניפולציית ביטים.

ששת הפעולות הבסיסיות

פעולהסמל Cדוגמה (בינארי)תוצאהשימוש
AND&1010 & 11001000בדיקת/ניקוי ביטים
OR|1010 | 11001110הדלקת ביטים
XOR^1010 ^ 11000110הפיכת ביטים, Encryption
NOT~~10100101...היפוך כל הביטים
Shift Left<<0001 << 31000כפל ב-2^n
Shift Right>>1000 >> 20010חלוקה ב-2^n
int a = 0b10110011; // 0xB3 int b = 0b11001010; // 0xCA printf("AND: 0x%X\n", a & b); // 0b10000010 = 0x82 printf("OR: 0x%X\n", a | b); // 0b11111011 = 0xFB printf("XOR: 0x%X\n", a ^ b); // 0b01111001 = 0x79 // Shift Left = כפל ב-2^n int x = 1; printf("1 << 3 = %d\n", x << 3); // 8 (כפל ב-2^3) printf("1 << 7 = %d\n", x << 7); // 128 // Shift Right = חלוקה ב-2^n printf("128 >> 3 = %d\n", 128 >> 3); // 16 // XOR טריקים: // x ^ x = 0 (איפוס ב-Assembly!) // x ^ 0 = x (ללא שינוי)
✏תרגול — Bitwise4 שאלות
שאלה 4.1
מה תוצאת 10 | 6? (10 בינארי = 1010, 6 = 0110)
✓ תשובה: א) 14. 1010 OR 0110: כל ביט שדלוק באחד מהם → 1110. ערך עשרוני: 8+4+2 = 14.
שאלה 4.2
ברצונך להכפיל x ב-8 בצורה המהירה ביותר. איזו פעולה?
✓ תשובה: א) x << 3. הזזה שמאלה ב-n = כפל ב-2^n. 2^3 = 8. פעולת Shift מהירה בהרבה מכפל מתמטי — מחזור שעון אחד ב-CPU.
שאלה 4.3
מה תוצאת 1 ^ 1 ^ 1 (XOR שרשורי)?
✓ תשובה: ג) 1. 1 ^ 1 = 0 (זהים → 0). 0 ^ 1 = 1 (שונים → 1). תכונת XOR: a^a=0, a^0=a.
שאלה 4.4
כיצד ניתן לאפס x ללא השמה x=0 (טריק Assembly)?
✓ תשובה: ג) x = x ^ x. XOR של מספר עם עצמו = כל הביטים מתאפסים (כל ביט זהה לעצמו → XOR נותן 0). ב-Assembly: XOR EAX, EAX — פעולת האיפוס המהירה ביותר!
// פרק 5

Bitmask Idioms — Set, Clear, Toggle, Check

ארבעת הניתוחים שחוזרים בכל קוד low-level, ב-TCP Flags, בהרשאות קבצים, ובכל Flags. חובה לשנן אותם.

// הגדרת Flags (כל אחד ביט אחד) #define FLAG_READ (1 << 0) // 0b00000001 = 0x01 #define FLAG_WRITE (1 << 1) // 0b00000010 = 0x02 #define FLAG_EXEC (1 << 2) // 0b00000100 = 0x04 #define FLAG_HIDDEN (1 << 3) // 0b00001000 = 0x08 uint8_t perms = 0; // כל הפלאגים כבויים // SET BIT — הדלקת ביט (OR עם ה-Mask) perms |= FLAG_READ; // הדלק READ perms |= FLAG_WRITE | FLAG_EXEC; // הדלק WRITE וEXEC // perms עכשיו: 0b00000111 = 0x07 // CHECK BIT — בדיקת ביט (AND עם ה-Mask → != 0 אם דלוק) if (perms & FLAG_READ) printf("Read!\n"); // ✓ if (perms & FLAG_HIDDEN) printf("Hidden!\n"); // ✗ לא יודפס // CLEAR BIT — כיבוי ביט (AND עם NOT של ה-Mask) perms &= ~FLAG_EXEC; // כבה EXEC // ~FLAG_EXEC = 0b11111011 // TOGGLE BIT — היפוך ביט (XOR עם ה-Mask) perms ^= FLAG_WRITE; // הפוך WRITE // READ BITS — חילוץ ערך משדה uint8_t data = 0b10110100; uint8_t high = (data >> 4) & 0x0F; // → 0b1011 = 11 (Nibble גבוה) uint8_t low = data & 0x0F; // → 0b0100 = 4 (Nibble נמוך)
// CHEAT SHEET — Bitwise Idioms
Set bit n: x |= (1 << n)
Clear bit n: x &= ~(1 << n)
Toggle bit n: x ^= (1 << n)
Check bit n: (x >> n) & 1 ← תוצאה: 0 או 1
Check bit n: x & (1 << n) ← תוצאה: 0 או != 0
Extract n bits from pos p: (x >> p) & ((1 << n) - 1)

TCP Flags — דוגמה מהעולם האמיתי

// TCP Flags — כל ביט הוא דגל #define TCP_FIN (1 << 0) // 0x01 — סיום חיבור #define TCP_SYN (1 << 1) // 0x02 — בקשת חיבור #define TCP_RST (1 << 2) // 0x04 — איפוס חיבור #define TCP_ACK (1 << 4) // 0x10 — Acknowledgment // ניתוח Packet שקיבלנו uint8_t flags = 0x12; // SYN-ACK (0x02 | 0x10) if (flags & TCP_SYN) printf("SYN set\n"); // ✓ if (flags & TCP_ACK) printf("ACK set\n"); // ✓ if (flags & TCP_FIN) printf("FIN set\n"); // ✗ // Three-Way Handshake: // 1. Client → Server: SYN (0x02) // 2. Server → Client: SYN + ACK (0x12) ← זה ה-Packet שלנו // 3. Client → Server: ACK (0x10)
✏תרגול — Bitmask Idioms4 שאלות
שאלה 5.1
כיצד נדליק את ביט 3 (אינדקס 3, ערך 8) ב-flags?
✓ תשובה: ד) flags |= (1 << 3). OR עם מסיכה שבה רק ביט 3 דלוק מבטיח שהביט הזה ידלק, בלי לשנות שאר הביטים.
שאלה 5.2
כיצד נכבה את ביט 0 (הימני ביותר) ב-flags?
✓ תשובה: ג) flags &= ~1. ~1 = 0b11111110. AND עם זה מכבה ביט 0 ושומר את כל השאר. XOR (א) היה מבצע Toggle — לא Clear.
שאלה 5.3
מה הבעיה בקוד: if (flags & 4 == 4)?
✓ תשובה: א. בשפת C, == קודם ל-&. לכן: 4==4 = 1, אז flags & 1 — לא מה שרצינו! תיקון: if ((flags & 4) == 4) עם סוגריים.
שאלה 5.4
לאיזו פעולה לוגית דומה XOR כשפועל על ביטים בודדים?
✓ תשובה: ב) !=. XOR מחזיר 1 אם הביטים שונים, 0 אם זהים — זו בדיוק ההתנהגות של !=. לכן XOR מאפס (0^0=0, 1^1=0) ומדליק לשונים (0^1=1, 1^0=1).
// פרק 6

Bit Fields ו-Endianness

Bit Fields מאפשרים שדות ב-Struct בגודל ביטים. Endianness קובע סדר הbytes בזיכרון — קריטי לניתוח פרוטוקולים.

Bit Fields

// IP Header — כל שדה בגודלו האמיתי typedef struct { unsigned int version : 4; // ביטים 0-3 unsigned int ihl : 4; // ביטים 4-7 unsigned int length : 16; // ביטים 16-31 } IPHeader; printf("sizeof(IPHeader) = %zu\n", sizeof(IPHeader)); // 4 bytes! // Bit Fields + Union = גישה לשדות + Raw Bytes typedef union { struct { uint8_t fin : 1; uint8_t syn : 1; uint8_t rst : 1; uint8_t ack : 1; uint8_t res : 4; // Reserved } flags; uint8_t raw; // גישה כ-Byte שלם } TCPFlags; TCPFlags f; f.raw = 0x12; // SYN + ACK printf("SYN: %u\n", f.flags.syn); // 1 printf("ACK: %u\n", f.flags.ack); // 1
⚠️ Bit Fields ו-Portability
סדר הBits בתוך Byte הוא Implementation Defined — הקומפיילר/ארכיטקטורה קובעים. לניתוח פרוטוקולים "אמיתי" עדיף Bitmask ידני. ב-CTF ובקוד קיים — תמצאו גם Bit Fields.

Endianness

Little EndianBig Endian (Network Order)
הגדרהByte הנמוך בכתובת הנמוכהByte הגבוה בכתובת הנמוכה
0x1234567878 56 34 12 (בזיכרון)12 34 56 78 (בזיכרון)
שימושx86, x64 (המחשב שלך)פרוטוקולי רשת (TCP/IP, DNS)
// המרה בין Network Byte Order ל-Host Byte Order #include <arpa/inet.h> // Linux uint32_t host_val = 0x12345678; uint32_t net_val = htonl(host_val); // Host TO Network Long uint32_t back = ntohl(net_val); // Network TO Host Long // לערכי 16 ביט (Ports): uint16_t port = 80; uint16_t net_port = htons(port); // 80 → ייצוג רשתי // לכן: כשקוראים מ-Packet רשת — תמיד ntohl/ntohs לפני שימוש!
✏תרגול — Bit Fields ו-Endianness3 שאלות
שאלה 6.1
union { uint16_t n; uint8_t b[2]; } u; u.n = 0x1234; על מחשב Little Endian — מה הם u.b[0] ו-u.b[1]?
✓ תשובה: ג. Little Endian: LSB (הbyte הנמוך = 0x34) נשמר בכתובת הנמוכה. לכן b[0]=0x34, b[1]=0x12. Big Endian היה הפוך: b[0]=0x12.
שאלה 6.2
מה תפקיד הפונקציה htons()?
✓ תשובה: א. htons = Host TO Network Short. על מחשב Little Endian: הופכת את סדר הbytes. Port 80 → 0x5000 בייצוג הרשתי. ntohl/ntohs = הכיוון ההפוך.
שאלה 6.3
מה הגישה ל-uint8_t b[0] בזיכרון תחזיר אם שמרנו 0x00000001 ב-uint32_t על מחשב Little Endian?
✓ תשובה: ב) 1. Little Endian = LSB בכתובת הנמוכה. 0x00000001: הbyte הנמוך הוא 0x01=1. אם b[0]==1 → המחשב Little Endian. זה הטריק הקלאסי לבדיקת Endianness.
// תרגול נוסף

תרגול נוסף — ביטים: Shift ו-Struct Alignment

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

✏תרגול נוסף3 שאלות
שאלה 7.1 [בינוני]
מה ערך perms אחרי perms = 0x0F; perms &= ~0x05;?
✓ תשובה נכונה: ב — ~0x05 = ...11111010. 0x0F(1111) & 0xFA = 1010 = 0x0A. תבנית 'ניקוי דגלים': AND עם השלילה של המסכה מכבה ביטים נבחרים.
שאלה 7.2 [קל]
מה מדפיס printf("%d", 5 << 2);?
✓ תשובה נכונה: ג — 5 << 2 = 5 × 2² = 5 × 4 = 20. בבינארי: 101 → 10100 = 20. Shift Left ב-N = כפל ב-2^N (מהיר יותר מכפל רגיל).
שאלה 7.3 [קשה]
מה sizeof(struct A)?
struct A {
  char  a;   // 1
  int   b;   // 4
  char  c;   // 1
};
✓ תשובה נכונה: ג — יישור: a(1)+pad(3)+b(4)+c(1)+pad(3)=12. ה-int חייב להתחיל בכתובת מתחלקת ב-4. סדר השדות משפיע! החלפת a ו-c לא תעזור כאן.
// Structs מעמיק (ממוזג)

struct: הגדרה, גישה ו-sizeof

Struct קובץ שדות מסוגים שונים יחד. הכתובת של ה-struct היא הכתובת של השדה הראשון. שאר השדות ממוקמים ב-Offsets הולכים וגדלים.

הגדרה ושימוש

// ─── הגדרה ─────────────────────────────────────────────── struct Point { int x; // offset 0 int y; // offset 4 }; // ─── typedef: שם קצר ────────────────────────────────── typedef struct { int id; // offset 0 float score; // offset 4 char name[20]; // offset 8 } Student; // ─── יצירה ושימוש ───────────────────────────────────── Student s; s.id = 1; s.score = 95.5f; // strcpy(s.name, "Alice"); // ─── יצירה עם Initializer ───────────────────────────── Student t = {2, 87.3f, "Bob"}; // ─── sizeof ─────────────────────────────────────────── printf("%zu\n", sizeof(Student)); // = 4 + 4 + 20 = 28 bytes // ─── מערך של Structs ────────────────────────────────── Student class[30]; class[0].id = 1; // &class[1] = &class[0] + sizeof(Student) = &class[0] + 28
הרחבה Struct ב-Assembly — דוגמה מ-RE ›
; C: s.score = 95.5f; (s ב-Stack, offset של score = 4) ; Assembly: lea eax, [ebp-28] ; eax = &s (כתובת ה-struct ב-Stack) movss xmm0, 95.5 ; xmm0 = 95.5f (float) movss [eax+4], xmm0 ; s.score = 95.5 ← +4 = offset של score ; זיהוי ב-RE: כשרואים [reg+4], [reg+8], [reg+12] ← גישות לשדות Struct!
✏תרגול עצמי3 שאלות
שאלה 1.1
מה הכתובת של s.score אם s נמצא בכתובת 0x1000 ו-score הוא השדה השני אחרי int id?
✓ תשובה: ג) 0x1004 — id הוא int (4 bytes) ב-offset 0. score מתחיל ב-offset 4. כתובת: 0x1000 + 4 = 0x1004.
שאלה 1.2
מה הגישה ל-struct דרך Pointer? אם Student *p = &s, כיצד ניגש ל-id?
✓ תשובה: ג) p->id — דרך Pointer משתמשים ב-arrow operator (->). שקול ל-(*p).id. דרך משתנה רגיל: s.id.
Dry Run — sizeof
typedef struct { int id; // offset 0 float score; // offset 4 char name[20]; // offset 8 } Student; printf("%zu", sizeof(Student));מה יודפס?
✓ תשובה: ד) 28 — int(4) + float(4) + char[20](20) = 28 bytes. בדוגמה זו אין Padding כי כל שדה מיושר כבר.
// Structs מעמיק (ממוזג)

Padding ו-Alignment: למה sizeof גדול ממה שציפינו

Alignment הוא הכלל שכתובת של משתנה מסוג T חייבת להיות מחולקת ב-sizeof(T). הקומפיילר מוסיף Padding (bytes ריקים) בין שדות — זה גורם לstruct להיות גדול יותר מסכום שדותיו!

כללי Alignment

טיפוסAlignmentהסבר
char1כל כתובת תקפה
short2כתובת חייבת להיות זוגית
int / float4כתובת חייבת להיות כפולה של 4
double / pointer (64bit)8כתובת חייבת להיות כפולה של 8
// ─── דוגמה עם Padding ───────────────────────────────── struct Mixed { char a; // offset 0 (1 byte) // [Padding: 3 bytes] ← כדי ליישר int ל-offset 4 int b; // offset 4 (4 bytes) char c; // offset 8 (1 byte) // [Padding: 3 bytes] ← Trailing Padding (כפולה של 4) }; // sizeof(struct Mixed) = 12 (לא 6!) // ─── שינוי סדר שדות חוסך Padding! ────────────────── struct Bad { struct Good { char a; // off 0 int b; // off 0 // pad 3 char a; // off 4 int b; // off 4 char c; // off 5 char c; // off 8 // pad 2 // pad 3 }; // sizeof = 8 }; // sizeof = 12
הרחבה #pragma pack — ביטול Padding ›
#pragma pack(1) // ← ביטול Padding (MSVC) struct NoAlign { char a; int b; char c; // sizeof = 6 בדיוק! }; #pragma pack() // ← חזרה לברירת מחדל __attribute__((packed)) // ← GCC/Clang struct NoAlign2 { char a; int b; char c; } __attribute__((packed));

⚠️ גישה ל-int לא מיושר = Unaligned Access → איטי או Crash ב-ARM!

offsetof: מיקום שדה בזיכרון

#include <stddef.h> typedef struct { char a; // offset 0 int b; // offset 4 (אחרי 3 bytes padding) char c; // offset 8 double d; // offset 16 (אחרי 7 bytes padding) } Example; printf("%zu\n", offsetof(Example, a)); // 0 printf("%zu\n", offsetof(Example, b)); // 4 printf("%zu\n", offsetof(Example, c)); // 8 printf("%zu\n", offsetof(Example, d)); // 16 printf("%zu\n", sizeof(Example)); // 24
✏תרגול עצמי4 שאלות
שאלה 2.1 — Padding חישוב
struct A { char x; short y; int z; };מה sizeof(struct A)?
✓ תשובה: ד) 8 — char(0), PAD(1), short(2..3), int(4..7). char+PAD=2 כי short דורש alignment 2. int כבר מיושר ל-offset 4. sizeof=8.
שאלה 2.2 — double+char
struct C { double d; char c; int i; };מה sizeof(struct C)?
✓ תשובה: ג) 16 — double(0..7), char(8), PAD(9..11) כי int דורש alignment 4, int(12..15). sizeof=16. גם מסתיים כפולה של 8 (alignment הגדול ביותר).
שאלה 2.3 — סדר חשוב
מה ההבדל ב-sizeof בין struct{char;int;char;} לבין struct{int;char;char;}?
✓ תשובה: ג) 12 vs 8 — Bad: char│PAD×3│int│char│PAD×3 = 12. Good: int│char│char│PAD×2 = 8. סדר שדות משפיע ישירות על גודל struct!
שאלה 2.4 — offsetof
typedef struct { char a; // offset ? int b; // offset ? char c; // offset ? double d; // offset ? } Example; printf("%zu", offsetof(Example, d));מה יודפס?
✓ תשובה: ב) 16 — char(0), PAD×3, int(4..7), char(8), PAD×7 (double דורש alignment 8, הבא שמתחלק ב-8 אחרי offset 9 = offset 16), double(16..23).
// Structs מעמיק (ממוזג)

union: שיתוף זיכרון בין שדות

union הוא כמו struct אבל כל השדות חולקים את אותו מקום בזיכרון. גודל union = גודל השדה הגדול ביותר. רק שדה אחד תקף בכל זמן — נפוץ ב-Protocol parsing, Type Punning, ו-Exploitation.

// ─── union קלאסי ───────────────────────────────────────── union Data { int i; // 4 bytes float f; // 4 bytes char c[4]; // 4 bytes }; // sizeof(union Data) = 4 (גדול ביניהם = 4) // כל השדות מתחילים ב-offset 0! union Data d; d.i = 0x41424344; // כתוב int printf("%c\n", d.c[0]); // קרא כ-char[0] // ב-Little-Endian: d.c[0]='D'(0x44), d.c[1]='C', d.c[2]='B', d.c[3]='A' // ─── Type Punning: קריאת float כ-int ────────────────── union FloatInt { float f; int i; }; union FloatInt fi; fi.f = 3.14f; printf("%08X\n", fi.i); // הייצוג הבינארי של 3.14f = 0x4048F5C3 // ─── Tagged Union (שכיח ב-C ל-OOP-like) ────────────── typedef struct { int type; // 0=int, 1=float, 2=string union { int ival; float fval; char *sval; } data; } Value;
🔑 חשוב לגאמא — Type Confusion ב-CTF
Type Confusion מתרחש כשתוקף גורם לתוכנה לפרש שדה union כסוג שגוי:
◄ תוכנה כותבת ל-data.ival (int), ואחר כך קוראת data.sval (char*) כ-Pointer
◄ תוקף שולט ב-ival → שולט ב-Pointer שdata.sval יצביע עליו → Read/Write לכל כתובת
◄ נפוץ ב-Browser Exploitation: JavaScript engine objects עם Tagged Union
✏תרגול עצמי3 שאלות
שאלה 3.1 — sizeof union
union Big { char c; // 1 byte int i; // 4 bytes double d; // 8 bytes }; printf("%zu", sizeof(union Big));מה יודפס?
✓ תשובה: א) 8 — sizeof(union) = גודל השדה הגדול ביותר = sizeof(double) = 8. כל השדות חולקים את אותם 8 bytes.
שאלה 3.2 — Type Punning
union { int i; char c[4]; } u; u.i = 0x12345678; // Little-Endian x86 printf("%02X %02X", u.c[0], u.c[1]);מה יודפס?
✓ תשובה: א) 78 56 — ב-Little-Endian, ה-LSB (0x78) נמצא בכתובת הנמוכה ביותר (c[0]), הבא (0x56) ב-c[1]. 0x12345678 → c[0]=0x78, c[1]=0x56.
שאלה 3.3 — ההבדל struct/union
מה ההבדל המרכזי בין struct לבין union?
✓ תשובה: א) — struct: גודל = סכום כל השדות (+Padding). union: גודל = שדה הגדול ביותר. כתיבה לשדה אחד ב-union דורסת את האחרים.
// Structs מעמיק (ממוזג)

Bitfields: ניהול ביטים בתוך שדות

Bitfields מאפשרים להגדיר שדות שתופסים מספר ביטים בלבד — לא byte שלם. נפוצים מאוד ב-Network Protocol Headers, Hardware Registers, ו-Flags Fields.

// ─── Bitfield קלאסי: פרוטוקול רשת ────────────────────── struct IPHeader { unsigned int version : 4; // 4 ביטים — IP version (4 או 6) unsigned int ihl : 4; // 4 ביטים — Internet Header Length unsigned int tos : 8; // 8 ביטים — Type of Service unsigned int tot_len : 16; // 16 ביטים — Total Length }; // sizeof(struct IPHeader) = 4 (בסה"כ 32 ביטים = 4 bytes) struct IPHeader hdr; hdr.version = 4; // IPv4 hdr.ihl = 5; // Header = 5 * 4 = 20 bytes hdr.tot_len = 1500; // ─── Bitfield עם Flags ──────────────────────────────── struct Permissions { unsigned int read : 1; // ביט 0 unsigned int write : 1; // ביט 1 unsigned int execute : 1; // ביט 2 unsigned int : 29; // 29 ביטים padding }; struct Permissions perm = {1, 0, 1}; // read + execute printf("%d\n", perm.read); // 1 printf("%d\n", perm.write); // 0 printf("%d\n", perm.execute); // 1
! מגבלות Bitfield — חשוב! ›

◄ sizeof עם Bitfield — הקומפיילר מעגל לגודל int (4 bytes) גם אם סה"כ הביטים קטן יותר

◄ אי אפשר לקחת כתובת — &hdr.version → שגיאת קומפילציה!

◄ סדר ביטים — האם ביטים ממלאים מ-LSB ל-MSB או הפוך — Implementation Defined! עבור Network Protocols: תמיד בדקו byte order ו-bit order.

◄ לProtocol Parsing אמיתי: השתמשו ב-packed struct + bit manipulation ידני.

✏תרגול עצמי3 שאלות
שאלה 4.1 — sizeof Bitfield
struct Flags { unsigned int a : 3; unsigned int b : 5; unsigned int c : 8; }; printf("%zu", sizeof(struct Flags));מה יודפס?
✓ תשובה: ב) 4 — סה"כ 3+5+8=16 ביטים = 2 bytes, אבל הקומפיילר מעגל ל-sizeof(unsigned int) = 4 bytes. Bitfields נארזים בתוך יחידת ה-underlying type.
שאלה 4.2 — מגבלת Bitfield
מה קורה אם ניסה לקחת כתובת של שדה Bitfield: &hdr.version?
✓ תשובה: ג) שגיאת קומפילציה — ביטים אינם ניתנים לכתובת. כתובת היא granularity של byte. לא ניתן להגדיר pointer ל-Bitfield.
שאלה 4.3 — שימוש ב-Bitfields
מהו היתרון העיקרי של Bitfields לעומת שימוש ב-#define ו-bit masking ידני?
✓ תשובה: ב) — Bitfield: hdr.version = 4 — פשוט וקריא. Mask: raw = (raw & ~0xF) | (4 & 0xF) — מסובך. בפועל: לProtocol parsing ב-Network Code, masking ידני portable יותר.
// Structs מעמיק (ממוזג)

Endianness: סדר Bytes בזיכרון

Endianness קובע באיזה סדר נשמרים ה-bytes של ערך רב-בייטי בזיכרון. קריטי ל-Reverse Engineering וב-Network Programming.

סוגהגדרה0x12345678 בזיכרוןנפוץ ב-
Little-EndianLSB (Byte הקטן) קודם78 56 34 12 (הפוך!)Intel x86, x64, ARM (LE mode)
Big-EndianMSB (Byte הגדול) קודם12 34 56 78 (סדר טבעי)Network Protocols, MIPS, Sparc
// ─── בדיקת Endianness ───────────────────────────────── union { int i; char b[4]; } test; test.i = 1; if (test.b[0] == 1) printf("Little-Endian\n"); // x86/x64 else printf("Big-Endian\n"); // ─── 0x12345678 ב-Little-Endian ────────────────────── int val = 0x12345678; // בזיכרון (כתובות עולות): // Addr 0: 0x78 ← Least Significant Byte (LSB) // Addr 1: 0x56 // Addr 2: 0x34 // Addr 3: 0x12 ← Most Significant Byte (MSB) // ─── Network Byte Order (Big-Endian) ───────────────── #include <arpa/inet.h> uint32_t host_val = 0x12345678; uint32_t network_val = htonl(host_val); // Host TO Network Long uint32_t back = ntohl(network_val); // Network TO Host Long
🔑 חשוב לגאמא — Endianness ב-RE
◄ ב-IDA/Ghidra: כשרואים push 0x12345678 ב-Assembly — זה הערך כמו שהוא
◄ ב-Hex Dump של זיכרון x86: הbytes הפוכים! 78 56 34 12 = 0x12345678
◄ בNetwork Packet Analysis (Wireshark): Big-Endian! Port 0x0050 = 80
◄ Format String Exploit: כותבים כתובת ב-Little-Endian לזיכרון
✏תרגול עצמי3 שאלות
שאלה 5.1 — Little-Endian
ב-Little-Endian x86, הערך 0xDEADBEEF נמצא בזיכרון החל מכתובת 0x1000. מה הבית בכתובת 0x1000?
✓ תשובה: ד) 0xEF — ב-Little-Endian, ה-LSB (Least Significant Byte) מאוחסן בכתובת הנמוכה ביותר. ב-0xDEADBEEF, ה-LSB הוא 0xEF (הבית הימני ביותר).
שאלה 5.2 — Network Byte Order
Network Byte Order = Big-Endian. אם Port מספר 80 נשלח ברשת, איך נראה ב-hex dump של הpacket?
✓ תשובה: ג) 00 50 — Port 80 = 0x0050. ב-Big-Endian (Network): MSB קודם → 00 50. אם היה Little-Endian: 50 00. htons(80) = 0x5000 (בייצוג LE) אבל על הרשת נשלח 00 50.
שאלה 5.3 — Endian ב-RE
ב-IDA/Ghidra, Hex Dump מראה: 78 56 34 12 בכתובת 0x8048000. איזה int מאוחסן שם (x86)?
✓ תשובה: ב) 0x12345678 — ב-Little-Endian x86: 78=LSB בכתובת נמוכה. קוראים הפוך: 78 56 34 12 → 0x12345678. זוהי אחת הבלבלות הנפוצות ב-RE!
// Structs מעמיק (ממוזג)

Struct ב-Assembly: שחזור Layout מ-RE

כשעושים Reverse Engineering לבינארי, שחזור Struct Layout מ-Assembly הוא מיומנות ליבה. כל [reg+N] נותן לנו רמז על offset ב-Struct.

; ─── קוד Assembly לניתוח ────────────────────────────────── process_obj: ; [ebp+8] = Pointer לObject כלשהו push ebp mov ebp, esp mov eax, [ebp+8] ; eax = Pointer לObject mov ecx, [eax+0] ; (A) קרא int מ-Offset 0 cmp ecx, 0 jz .skip mov edx, [eax+4] ; (B) קרא int מ-Offset 4 add edx, 1 mov [eax+4], edx ; (C) כתוב int ל-Offset 4 lea ebx, [eax+8] ; (D) קח כתובת Offset 8 push ebx call strlen ; (E) strlen → Offset 8 הוא String! add esp, 4 mov [eax+28], al ; (F) כתוב byte ל-Offset 28 .skip: pop ebp ret
הרחבה שחזור ה-Struct — ניתוח מלא ›

(A) offset 0 → int — נקראת ובגדולה (cmp) → כנראה 'type' או 'flags'

(B/C) offset 4 → int — מוגדל ב-1 (counter) → כנראה 'count' או 'refcount'

(D/E) offset 8 → LEA (כתובת) → מועבר ל-strlen → char array! גודל = 28-8=20

(F) offset 28 → byte אחד → char

typedef struct { int type; // offset 0 int count; // offset 4 char name[20]; // offset 8 char status; // offset 28 } Object; // sizeof ≈ 29 → 32 עם Padding
✏תרגול עצמי — גאמא סגנון3 שאלות
שאלה 6.1 — Dry Run sizeof
struct D { char a; char b; char c; char d; }; printf("%zu", sizeof(struct D));מה יודפס?
✓ תשובה: ד) 4 — כל השדות הם char (alignment 1). אין Padding כלל! char│char│char│char = 4 bytes בדיוק.
שאלה 6.2 — ניתוח Assembly
ב-Assembly רואים: mov eax, [ebx+12] ואחר כך test eax, eax ו-jz label. מה אפשר להסיק על ה-struct?
✓ תשובה: ד) — mov eax, [ebx+12] = קריאת 4 bytes (dword) → int או pointer. test eax, eax = בדיקת NULL/0. הסיק: שדה int/pointer ב-offset 12 שמשמש כ-flag או pointer שנבדק.
שאלה 6.3 — גאמא סגנון מלא
struct X { char a; // offset ? int b; // offset ? short c; // offset ? }; // מה sizeof(struct X) ומה layout בזיכרון?
✓ תשובה: א) sizeof=12 — char(0), PAD×3 (int דורש alignment 4), int(4..7), short(8..9), PAD×2 (הstruct חייב להסתיים ב-multiple של 4 = alignment הגדול). sizeof=12.
// Structs מעמיק (ממוזג)

תרגול נוסף — רשתות: Subnetting, ARP ו-Routing

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

✏תרגול נוסף3 שאלות
שאלה 7.1 [בינוני]
מחשב עם IP 10.0.0.5/8 שולח ל-10.0.1.100. מה יקרה?
✓ תשובה נכונה: א — מסכה /8 → 8 ביטים לרשת: כל 10.x.x.x היא אותה רשת. המקור והיעד ברשת 10.0.0.0/8 → שליחה ישירה (ARP), ללא ניתוב דרך Gateway.
שאלה 7.2 [בינוני]
מה זו מתקפת ARP Spoofing ואיך מגנים מפניה?
✓ תשובה נכונה: א — ARP חסר אימות → תוקף שולח ARP Reply 'אני ה-Gateway' → תעבורה עוברת דרכו (MITM). הגנה: DAI, Static ARP, ו-Encryption מקצה לקצה.
שאלה 7.3 [קשה]
כמה כתובות Host זמינות ברשת /26?
✓ תשובה נכונה: ב — /26: 32-26=6 ביטי Host → 2^6=64 כתובות. פחות Network ו-Broadcast = 62. זכור: /24=254, /25=126, /26=62, /27=30.
// אתגר מעשי

אתגר מעשי — מבנים, Padding ו-Endianness

שיא השיעור: לרדת לרמת-המכונה — C, אסמבלי, היפוך. ההגשה נבדקת אוטומטית.

T2 · בסיסי200 נק'~15 דק'algorithmic
היפוך Endianness (32-bit)
ממש swap32(n) שהופך את סדר הבתים של מספר 32-סיביות ללא-סימן (big↔little endian). למשל 0x12345678 → 0x78563412.
דוגמאות
hex(swap32(0x12345678)) → '0x78563412'
hex(swap32(0xFF)) → '0xff000000'
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס היפוך Endianness (32-bit) ›
def swap32(n: int) -> int: return (((n & 0xFF) << 24) | ((n & 0xFF00) << 8) | ((n >> 8) & 0xFF00) | ((n >> 24) & 0xFF))
T3 · בינוני350 נק'~25 דק'algorithmic
חישוב sizeof עם Padding
ממש struct_size(fields) המחשב את sizeof של struct ב-C לפי כללי היישור. fields היא רשימת טיפוסים; היישור הטבעי שווה לגודל: char=1, short=2, int=4, long/double/ptr=8, float=4. כל שדה מיושר לגבול הגודל שלו (padding בין שדות), ובסוף המבנה מרופד לגבול השדה הגדול ביותר (tail padding).
דוגמאות
struct_size(['char','int','char']) → 12
struct_size(['char','char','int']) → 8
struct_size(['double','char']) → 16
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס חישוב sizeof עם Padding ›
def struct_size(fields: list) -> int: sz = {'char':1,'short':2,'int':4,'long':8,'double':8,'ptr':8,'float':4} off = 0; maxa = 1 for f in fields: s = sz[f]; maxa = max(maxa, s) if off % s: off += s - (off % s) # padding ליישור השדה off += s if off % maxa: off += maxa - (off % maxa) # tail padding ליישור המבנה return off
// סיכום

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

✓מה למדנו בשיעור 25
  • Struct: הגדרה, Dot Operator, אתחול עם שמות שדות
  • typedef: קיצורי דרך לשמות Struct — קוד נקי ומקצועי
  • Padding ו-Alignment: הקומפיילר מוסיף bytes ריקים ליישור שדות בזיכרון
  • Struct Pointers ו-Arrow Operator (->): ptr->field = (*ptr).field
  • Union: כל השדות חולקים את אותו מקום — גודל = גדול ביותר
  • Union לניתוח פרוטוקולים: גישה לשדות לפי שם + גישה ל-Raw Bytes
  • AND, OR, XOR, NOT, Shift Left/Right — ויישומיהן
  • Bitmask: Set=OR, Clear=AND+NOT, Toggle=XOR, Check=AND
  • TCP Flags ניתוח: כל Flag הוא ביט ב-Byte
  • Bit Fields: שדות בגודל ביטים ב-Struct — נוח לקריאה
  • Endianness: Little (x86) vs Big (רשת) — htonl/ntohl להמרה
לפני שיעור 26 (Assembly x86):
1. כתבו Struct שמייצג DNS Query Header — איזה Bit Fields נחוצים?
2. כתבו פונקציות Set/Clear/Toggle/Check לכל 8 ביטי uint8_t
3. בדקו: sizeof(struct { char a; int b; char c; }) על המחשב שלכם
4. שיעור 26: Assembly x86 — רגיסטרים, MOV, ADD ופקודות בסיסיות
שאלות תרגול ותשובות (10)

שאלה 1 · למה שקול הביטוי ptr->field?

ptr->field
  • א. (*ptr).field
  • ב. *(ptr.field)
  • ג. &ptr.field
  • ד. ptr + field
תשובה: א

החץ הוא קיצור תחבירי: קודם מפנים את המצביע ואז ניגשים לשדה. נקודה משמשת למשתנה עצמו, חץ למצביע אליו.

שאלה 2 · מה קורה בהשמה של struct שלם למשתנה אחר?

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

C מעתיק את כל הבתים (Shallow copy). זהירות: אם ה-struct מכיל מצביע, שני העותקים יצביעו לאותו בלוק זיכרון.

שאלה 3 · מדוע ה-struct הבא תופס 8 בתים ולא 5?

struct { char a; int b; };
  • א. הקומפיילר מוסיף Padding כדי ליישר את int לכתובת המתחלקת ב-4
  • ב. char תופס בפועל 4 בתים
  • ג. int תופס 7 בתים במערכות 64 ביט
  • ד. הקומפיילר שומר בית לסיום המחרוזת
תשובה: א

🎯 דגש למבחן — Padding: המעבד קורא זיכרון בבלוקים מיושרים, ולכן הקומפיילר מוסיף בתי ריפוד כדי שכל שדה יתחיל בכתובת המתחלקת בגודלו, וגודל ה-struct הכולל מתחלק ביישור הגדול ביותר. סידור השדות מהגדול לקטן מקטין את הריפוד.

שאלה 4 · מה ההבדל המרכזי בין struct ל-union מבחינת זיכרון?

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

ב-union כתיבה לשדה אחד דורסת את האחרים, כי כולם ממופים לאותה כתובת. זה שימושי לחיסכון בזיכרון ולפירוש אותם בתים בשתי דרכים.

שאלה 5 · איך בודקים Endianness של המחשב באמצעות union?

union { int i; char c[4]; } u;
u.i = 1;
if (u.c[0] == 1) { /* ? */ }
  • א. c[0]==1 מעיד על Little Endian — הבית הפחות משמעותי נשמר ראשון
  • ב. c[0]==1 מעיד על Big Endian
  • ג. הבדיקה תמיד נכשלת — union לא תומך במערכים
  • ד. התוצאה תלויה בגרסת הקומפיילר בלבד
תשובה: א

ב-Little Endian (Intel/AMD) הבית הנמוך נשמר בכתובת הנמוכה, ולכן הערך 1 מופיע ב-c[0]. בפרוטוקולי רשת משתמשים ב-Big Endian — ומכאן הצורך ב-htons/ntohs.

שאלה 6 · מה תוצאת החישוב?

10 | 6   // 1010 | 0110
  • א. 14
  • ב. 2
  • ג. 4
  • ד. 16
תשובה: א

OR מדליק כל ביט שדלוק לפחות באחד מהצדדים: 1110 = 14. לשם השוואה 10 & 6 = 2, ו-10 ^ 6 = 12.

שאלה 7 · מהי הדרך המהירה להכפיל את x ב-8?

x << 3
  • א. הזזה שמאלה ב-3 — כל הזזה בסיבית אחת מכפילה ב-2
  • ב. הזזה ימינה ב-3
  • ג. x & 8
  • ד. x ^ 8
תשובה: א

🎯 דגש למבחן — פעולות ביטיות: הזזה שמאלה ב-n שקולה לכפל ב-2ⁿ, והזזה ימינה לחלוקה שלמה ב-2ⁿ. במספרים חיוביים זה בטוח; בשליליים ובגלישה מעבר לרוחב הטיפוס ההתנהגות עלולה להפתיע.

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

if (flags & 4 == 4) { ... }
  • א. אופרטור ההשוואה קודם ל-& , ולכן בפועל מחושב flags & (4==4) — חסרים סוגריים
  • ב. אין בעיה, הקוד תקין
  • ג. צריך להשתמש ב-&& במקום &
  • ד. אי אפשר להשוות מסכות ביטים
תשובה: א

הכתיבה הנכונה היא if ((flags & 4) == 4). זו טעות קלאסית בשאלות מיון — קדימות אופרטורים בבדיקות מסכה.

שאלה 9 · איך מכבים ביט מסוים ב-flags?

flags &= ~(1 << n);
  • א. AND עם המסכה ההפוכה מאפס רק את הביט הרצוי ומשאיר את השאר
  • ב. flags |= (1 << n)
  • ג. flags ^= (1 << n)
  • ד. flags = 0
תשובה: א

הדלקה: flags |= (1<<n). כיבוי: flags &= ~(1<<n). היפוך: flags ^= (1<<n). בדיקה: flags & (1<<n).

שאלה 10 · מה נכון לגבי Bit Fields ב-C?

struct TcpFlags {
    unsigned int fin : 1;
    unsigned int syn : 1;
    unsigned int ack : 1;
};
  • א. הם מאפשרים להקצות מספר סיביות מדויק לשדה, אך סדר האריזה תלוי-קומפיילר
  • ב. כל שדה עדיין תופס 4 בתים מלאים
  • ג. אפשר לקחת כתובת של שדה bit field עם &
  • ד. הם נתמכים רק ב-C++
תשובה: א

Bit Fields חוסכים זיכרון ומתאימים לתיאור דגלי TCP או רגיסטרים, אבל סדר הביטים והריפוד אינם מוגדרים בתקן — ולכן בקוד רשת אמיתי מעדיפים מסכות ביטים מפורשות.