Essentials and Preprocessing

מבחנים

מבחנים אלו מכסים את יסודות שפת C והפרה-פרוססור -- מ-enum, struct ו-union, דרך מאקרואים ו-Include Guards, ועד typedef ו-inline. הכנה אידיאלית למיון סייבר בגאמא סייבר וליחידה 8200.

  • enum
  • struct
  • union
  • char & long
  • switch
  • atoi
  • #define
  • #include
  • #ifdef / #elif
  • Include Guards
  • inline
  • typedef
  • # & ##
טיפ: קראו את המדריך המלא למטה לפני שמתחילים את המבחנים -- הוא מכסה את כל הנושאים עם דוגמאות קוד וטבלאות השוואה.

בהצלחה!

הקדמה כללית

היכרות מעמיקה עם שפת C מהווה בסיס חיוני לכל מתכנת המעוניין לשלוט ביסודות תכנות מערכות ויישומי Embedded. שפת C כוללת תכונות בסיסיות כמו עבודה עם משתנים מסוגים שונים, בקרת זרימה באמצעות תנאים ולולאות, וכן מנגנונים מתקדמים יותר כמו enum, struct, union ועיבוד מקדים (Preprocessing).

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

במדריך זה נסקור את יסודות השפה, נעמיק בכלי הפרה-מעבד, ונלמד כיצד להשתמש בכל מנגנון באמצעות דוגמאות קוד מפורטות.

טיפוסי נתונים: char ו-long

טיפוס char ברוב המערכות מיועד לאחסן תו אחד, לרוב בהתאם לקידוד ASCII. בפועל, char הוא מספר שלם בגודל 8 ביט ולכן אפשר לבצע עליו חישובים אריתמטיים.

טיפוס long מוגדר כשלם בעל גודל גדול או שווה לגודל int. במערכות 32-ביט הוא לרוב בגודל 32 ביט, ובמערכות 64-ביט (בלינוקס) הוא עשוי להיות 64 ביט. כך ניתן לייצג מספרים גדולים יותר.

#include <stdio.h>

int main() {
    char c = 'A';
    printf("char: %c, value: %d\n", c, c);  // A, 65

    char d = c + 1;
    printf("d: %c, value: %d\n", d, d);    // B, 66

    long big = 2147483648L;
    printf("long: %ld\n", big);

    printf("sizeof(char)=%lu, sizeof(long)=%lu\n",
           sizeof(char), sizeof(long));
    return 0;
}
שימו לב: ב-C, טיפוס char יכול להיות signed או unsigned בהתאם למהדר. אם חשוב לכם הטווח, ציינו זאת במפורש.

enum – קבועים בעלי שמות

ב-C ניתן להגדיר אוסף של קבועים שלמים תחת שם אחד באמצעות enum. המהדר מקצה לכל שם ערך עוקב כברירת מחדל, החל מ-0. אפשר גם להצמיד ערכים ספציפיים לכל איבר.

כאשר משתמשים ב-enum מקבלים קוד קריא יותר מאשר שימוש במספרים "קסומים". אם איבר אחד מוגדר לערך מסוים, האיבר הבא יקבל ערך עוקב אוטומטית.

#include <stdio.h>

enum Status { IDLE, RUNNING, STOPPED = 10, PAUSED };
// IDLE=0, RUNNING=1, STOPPED=10, PAUSED=11

enum Color { RED, GREEN, BLUE };
// RED=0, GREEN=1, BLUE=2

int main() {
    enum Status s = PAUSED;
    printf("PAUSED = %d\n", s);       // 11
    printf("STOPPED = %d\n", STOPPED); // 10

    enum Color c = GREEN;
    printf("GREEN = %d\n", c);        // 1
    return 0;
}

struct – מבנים מורכבים

struct בשפת C נועד לאגד מספר משתנים מסוגים שונים תחת שם טיפוס יחיד. הגישה לשדות מתבצעת באמצעות סימון נקודה (.), ובמצביע באמצעות חץ (->).

struct הוא כלי יעיל לארגון נתונים הקשורים זה לזה. במקום להחזיק כמה משתנים מבולגנים, ניתן לארוז אותם במבנה אחד.

#include <stdio.h>

struct Student {
    char name[50];
    int  age;
    float grade;
};

int main() {
    struct Student s1 = {"Dan", 20, 95.5};

    printf("%s is %d years old, grade: %.1f\n",
           s1.name, s1.age, s1.grade);

    // שינוי שדה
    s1.age = 21;

    // גודל המבנה בזיכרון (כולל padding)
    printf("sizeof(Student) = %lu\n", sizeof(struct Student));
    return 0;
}

union – שיתוף זיכרון

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

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

#include <stdio.h>

union Data {
    int   i;
    float f;
    char  c;
};

int main() {
    union Data d;

    d.i = 42;
    printf("d.i = %d\n", d.i);        // 42

    d.f = 3.14f;
    printf("d.f = %.2f\n", d.f);      // 3.14
    printf("d.i = %d (corrupted!)\n", d.i);  // ערך לא צפוי!

    printf("sizeof(union Data) = %lu\n", sizeof(union Data));
    // שווה לגודל השדה הגדול ביותר (int או float = 4)
    return 0;
}

טבלת השוואה: struct מול union

תכונה struct union
הקצאת זיכרון סכום כל השדות (+ padding) גודל השדה הגדול ביותר
גישה מקבילית לשדות כן – כל השדות תקינים בו זמנית לא – רק שדה אחד תקין בכל רגע
שימוש אופייני אחסון מספר נתונים קשורים יחד חיסכון בזיכרון כשסוג הנתון משתנה
sizeof סכום (עם יישור) מקסימום מבין השדות

switch ו-default

פקודת switch מאפשרת לבחור בלוק קוד לביצוע בהתאם לערך של משתנה שלם או תו. במקום שרשרת ארוכה של if-else, ניתן להגדיר case לכל ערך רצוי. default מטפל במקרים שלא תאמו אף case.

כאשר ערך המשתנה תואם ל-case, מבוצעות ההוראות עד לפקודת break. ללא break יתרחש "נפילה" (fall-through) ל-case הבא.

#include <stdio.h>

int main() {
    int day = 3;

    switch (day) {
        case 1: printf("Sunday\n");    break;
        case 2: printf("Monday\n");    break;
        case 3: printf("Tuesday\n");   break;
        default: printf("Other day\n"); break;
    }

    // דוגמה ל-fall-through (ללא break):
    int x = 2;
    switch (x) {
        case 1:
        case 2:
        case 3:
            printf("x is 1, 2, or 3\n");
            break;
        default:
            printf("x is something else\n");
    }
    return 0;
}
זהירות: שכחת break היא טעות נפוצה ב-switch. ללא break, הביצוע ימשיך ל-case הבא (fall-through).

המרת מחרוזות למספרים: atoi

פונקציית atoi (מוגדרת ב-stdlib.h) ממירה מחרוזת (char*) למספר שלם int. שימושית כשצריך לקבל קלט טקסטואלי ולהפוך אותו לערך מספרי. atoi אינה מטפלת בשגיאות בקלט — אם נדרש טיפול מדויק יותר, ניתן להשתמש ב-strtol.

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *str1 = "456";
    int num1 = atoi(str1);
    printf("num1 = %d\n", num1);  // 456

    char *str2 = "abc";
    int num2 = atoi(str2);
    printf("num2 = %d\n", num2);  // 0 (no error indication!)

    char *str3 = "42xyz";
    int num3 = atoi(str3);
    printf("num3 = %d\n", num3);  // 42 (stops at first non-digit)
    return 0;
}

מבוא לפרה-מעבד (Preprocessor)

הפרה-מעבד פועל בשלב הראשון לפני הקומפילציה. הוא מטפל בכל הוראות ה-#include, #define, #if, #ifdef ועוד, ומפיק קובץ מקור מורחב בו כל המאקרואים מוחלפים ופקודות תנאי מוסרות או נכללות בהתאם.

שלבי הקומפילציה המלאים:

/* שלבי הקומפילציה */

Source Code (.c)
    │
    ▼
Preprocessor  →  Expanded source (macros replaced, includes merged)
    │
    ▼
Compiler      →  Assembly code (.s)
    │
    ▼
Assembler     →  Object code (.o)
    │
    ▼
Linker        →  Executable

מאקרואים: #define

מאקרו הוא מנגנון החלפת טקסט פשוט. ישנם שני סוגים:

מאקרו ללא פרמטר (Object-like)

מחליף שם בערך קבוע:

#define PI        3.14159
#define MAX_SIZE  100

double area = PI * r * r;
int arr[MAX_SIZE];

מאקרו עם פרמטרים (Function-like)

מדמה פונקציה אך מבצע החלפת טקסט בלבד:

#define MAX(a, b)    ((a) > (b) ? (a) : (b))
#define SQUARE(x)   ((x) * (x))

int m = MAX(5, 3);     // → ((5) > (3) ? (5) : (3)) → 5
int s = SQUARE(4);    // → ((4) * (4)) → 16
מלכודת הסוגריים: ללא סוגריים סביב הפרמטרים, התוצאה עלולה להיות שגויה:
#define SQUARE_BAD(x)   x * x
#define SQUARE_GOOD(x)  ((x) * (x))

int r1 = SQUARE_BAD(3+1);   // → 3+1 * 3+1 = 3+3+1 = 7  (WRONG!)
int r2 = SQUARE_GOOD(3+1);  // → ((3+1) * (3+1)) = 16   (CORRECT)

תופעות לוואי במאקרואים

מכיוון שמאקרו מבצע החלפת טקסט, כל ביטוי שמועבר כפרמטר עלול להתבצע מספר פעמים. זה מסוכן במיוחד עם אופרטורים כמו ++ ו---.

#define DOUBLE(x) ((x) + (x))

int a = 3;
int b = DOUBLE(a++);
// מתרחב ל: ((a++) + (a++))
// a מקודם פעמיים! התוצאה לא צפויה (undefined behavior)

// פתרון – להשתמש בפונקציית inline במקום:
static inline int double_val(int x) {
    return x + x;
}
int c = 3;
int d = double_val(c++); // בטוח! c מקודם פעם אחת בלבד
כלל אצבע: לעולם אל תעבירו ביטויים עם תופעות לוואי (++, --, קריאות לפונקציות) כפרמטרים למאקרו.

מאקרו מרובה שורות

כדי להגדיר מאקרו המשתרע על מספר שורות, יש להשתמש בתו \ (Backslash) בסוף כל שורה. תבנית do { ... } while(0) מאפשרת שימוש בטוח במאקרו בתוך משפטי if:

#define SWAP(a, b)  do { \
    int temp = (a);     \
    (a) = (b);          \
    (b) = temp;         \
} while(0)

// שימוש:
int x = 5, y = 10;
SWAP(x, y);
// עכשיו x=10, y=5
למה do { } while(0)? כדי שהמאקרו יתנהג כהוראה בודדת. בלי זה, שימוש בתוך if ללא סוגריים מסולסלים יגרום לשגיאות הידור.

ביטול מאקרו: #undef

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

#define BUFFER_SIZE 1024
// כאן BUFFER_SIZE שווה 1024

int buf1[BUFFER_SIZE];  // מערך בגודל 1024

#undef BUFFER_SIZE
#define BUFFER_SIZE 4096
// מכאן ואילך BUFFER_SIZE שווה 4096

int buf2[BUFFER_SIZE];  // מערך בגודל 4096

אופרטור # (Stringizing)

אופרטור # בתוך מאקרו ממיר פרמטר למחרוזת טקסט (string literal). הפרה-מעבד עוטף את הפרמטר במרכאות כפולות.

#define PRINT_VAR(x)  printf(#x " = %d\n", x)

int count = 42;
PRINT_VAR(count);
// מתרחב ל: printf("count" " = %d\n", count);
// פלט: count = 42

int total = 100;
PRINT_VAR(total);
// פלט: total = 100
הערה: ב-C, מחרוזות צמודות מתמזגות אוטומטית: "hello" " world" שקול ל-"hello world".

אופרטור ## (Token Pasting)

אופרטור ## מצמיד (paste) שני טוקנים לטוקן אחד. כך ניתן ליצור שמות משתנים או פונקציות בצורה דינמית בזמן קומפילציה.

#define MAKE_VAR(prefix, num)  prefix##num

int MAKE_VAR(var_, 1) = 10;  // → int var_1 = 10;
int MAKE_VAR(var_, 2) = 20;  // → int var_2 = 20;

#define DECLARE_FUNC(name)  \
    void func_##name() { printf("Called: %s\n", #name); }

DECLARE_FUNC(init)   // → void func_init() { printf("Called: %s\n", "init"); }
DECLARE_FUNC(close)  // → void func_close() { printf("Called: %s\n", "close"); }
שילוב: בדוגמה האחרונה שילבנו גם ## (ליצירת שם הפונקציה) וגם # (להמרת השם למחרוזת) באותו מאקרו.

#ifdef, #ifndef ו-#if defined

פקודת #ifdef NAME בודקת האם מאקרו בשם NAME הוגדר. #ifndef NAME בודקת שהמאקרו לא הוגדר. #if defined(NAME) מאפשרת שילוב תנאים לוגיים מורכבים.

// בדיקה פשוטה
#ifdef DEBUG
    printf("Debug mode is ON\n");
#endif

// בדיקה שלילית
#ifndef RELEASE
    printf("Not in release mode\n");
#endif

// תנאים מורכבים עם #if defined
#if defined(LINUX) && !defined(WINDOWS)
    printf("Linux only code\n");
#endif

#if defined(X86) || defined(ARM)
    printf("Supported architecture\n");
#endif
הבדל: #ifdef יכול לבדוק מאקרו בודד בלבד. #if defined(...) גמישה יותר – ניתן לשלב &&, || ו-!.

#if עם השוואות מספריות ו-#elif

#if יכול לבדוק ביטויים מספריים ולא רק האם מאקרו מוגדר. #elif (else-if) מאפשר שרשרת תנאים:

#define VERSION 3

#if VERSION < 2
    printf("Old version\n");
#elif VERSION == 2
    printf("Version 2\n");
#elif VERSION >= 3
    printf("Version 3 or later\n");  // ← this runs
#else
    printf("Unknown version\n");
#endif
// דוגמה נוספת: שילוב defined עם השוואות
#define ARCH_BITS 64

#if defined(USE_CUSTOM) && ARCH_BITS == 64
    typedef long word_t;
#elif ARCH_BITS == 32
    typedef int word_t;
#else
    #error "Unsupported architecture"
#endif
חשוב: מאקרו שלא הוגדר נחשב כ-0 בביטויי #if. לכן #if UNDEFINED_MACRO שקול ל-#if 0.

מאקרואים מוגדרים מראש (Predefined Macros)

המהדר מגדיר מראש מספר מאקרואים שימושיים, במיוחד לצרכי דיבוג ולוגים:

מאקרו תיאור דוגמת ערך
__FILE__ שם קובץ המקור הנוכחי "main.c"
__LINE__ מספר השורה הנוכחית 42
__DATE__ תאריך הקומפילציה "Mar 7 2026"
__TIME__ שעת הקומפילציה "14:30:00"
__func__ שם הפונקציה הנוכחית (C99) "main"
#include <stdio.h>

#define LOG(msg) \
    printf("[%s:%d] %s\n", __FILE__, __LINE__, msg)

int main() {
    printf("File: %s\n", __FILE__);
    printf("Line: %d\n", __LINE__);
    printf("Compiled: %s %s\n", __DATE__, __TIME__);
    printf("Function: %s\n", __func__);

    LOG("Starting program");
    // פלט: [main.c:12] Starting program
    return 0;
}

הוראת #error

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

#ifndef REQUIRED_CONFIG
    #error "REQUIRED_CONFIG must be defined! Use -DREQUIRED_CONFIG"
#endif

#if BUFFER_SIZE < 64
    #error "BUFFER_SIZE must be at least 64"
#endif

#if !defined(__linux__) && !defined(_WIN32)
    #error "Unsupported operating system"
#endif

#include – מרכאות מול סוגריים משולשים

הוראת #include מכלילה תוכן של קובץ אחר. ישנם שני סגנונות:

תחביר סדר חיפוש שימוש אופייני
#include "file.h" תיקייה מקומית תחילה, אח"כ ספריות מערכת קבצי כותרת של הפרויקט
#include <file.h> ספריות מערכת בלבד ספריות סטנדרטיות (stdio.h, stdlib.h)
#include <stdio.h>     // ספרייה סטנדרטית
#include <stdlib.h>    // ספרייה סטנדרטית
#include "myheader.h"  // קובץ מקומי של הפרויקט
#include "../utils.h"  // קובץ מקומי בתיקייה אחרת

Include Guards ו-#pragma once

כאשר קובץ כותרת נכלל מספר פעמים (ישירות או בעקיפין), עלולות להתרחש שגיאות הגדרה כפולה. שני פתרונות נפוצים:

גישה 1: Include Guards (קלאסי)

// my_header.h
#ifndef MY_HEADER_H
#define MY_HEADER_H

struct Point {
    int x, y;
};

void draw_point(struct Point p);

#endif // MY_HEADER_H

גישה 2: #pragma once (מודרני)

// my_header.h
#pragma once

struct Point {
    int x, y;
};

void draw_point(struct Point p);
מה עדיף? #pragma once פשוט וקצר יותר, אך אינו חלק מהתקן הרשמי. Include Guards עובדים בכל מהדר ללא יוצא מן הכלל.

#pragma – שימושים נוספים

#pragma היא הוראה לפרה-מעבד שנותנת "רמזים" ספציפיים למהדר. כל מהדר יכול לתמוך ב-pragma שונים. דוגמאות נפוצות:

#pragma pack – שליטה ביישור זיכרון

// ללא pragma pack – המהדר מוסיף padding:
struct Normal {
    char  a;   // 1 byte + 3 padding
    int   b;   // 4 bytes
};
// sizeof(Normal) = 8

// עם pragma pack – ללא padding:
#pragma pack(push, 1)
struct Packed {
    char  a;   // 1 byte
    int   b;   // 4 bytes
};
#pragma pack(pop)
// sizeof(Packed) = 5
זכרו: #pragma pack(push, 1) שומר את מצב היישור הנוכחי ומשנה ל-1. #pragma pack(pop) מחזיר למצב הקודם.

static ברמת הקובץ

כאשר מגדירים משתנה או פונקציה עם static מחוץ לכל פונקציה, נוצרת Internal Linkage – המשתנה או הפונקציה נגישים רק בקובץ הנוכחי.

זה שונה מ-static בתוך פונקציה, שם המשמעות היא שהמשתנה שומר על ערכו בין קריאות.

// ──── file1.c ────
static int counter = 0;           // נגיש רק ב-file1.c
static void helper() { counter++; } // נגישה רק ב-file1.c

void public_func() {  // נגישה מכל קובץ
    helper();
    printf("counter = %d\n", counter);
}

// ──── file2.c ────
static int counter = 0; // counter שונה! לא מתנגש עם file1.c

// ──── static בתוך פונקציה (שונה!) ────
void count_calls() {
    static int calls = 0;  // נשמר בין קריאות
    calls++;
    printf("Called %d times\n", calls);
}

typedef מול #define

שניהם יכולים ליצור "שם חדש" לטיפוס, אך יש הבדלים קריטיים:

typedef int* IntPtr;
#define INTPTR int*

IntPtr a, b;     // שניהם int* ✓
INTPTR c, d;     // c הוא int*, אבל d הוא int בלבד! ✗
// כי #define מחליף טקסט: int* c, d; → int *c, d;
תכונה typedef #define
מעובד ע"י המהדר (Compiler) הפרה-מעבד (Preprocessor)
בדיקת טיפוסים כן – מלאה לא – החלפת טקסט בלבד
מצביעים בטוח (ראו דוגמה למעלה) מסוכן – עלול ליצור באגים
תחום (Scope) בלוק / קובץ מנקודת ה-define עד undef או סוף הקובץ
שימוש מומלץ הגדרת שמות לטיפוסים קבועים, מאקרואים פונקציונליים

inline מול מאקרו פונקציונלי

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

// מאקרו – החלפת טקסט בלבד
#define MAX_MACRO(a, b)  ((a) > (b) ? (a) : (b))

// פונקציית inline – בדיקת טיפוסים מלאה
static inline int max_func(int a, int b) {
    return a > b ? a : b;
}
תכונה inline מאקרו פונקציונלי
בדיקת טיפוסים כן לא
תופעות לוואי בטוח – פרמטרים מחושבים פעם אחת מסוכן – פרמטרים מוחלפים כטקסט
דיבוג ניתן לדבג כרגיל קשה – אין שם פונקציה ב-debugger
החלטת המהדר המהדר רשאי שלא לבצע inline תמיד מתרחב
ביצועים דומים למאקרו (כשהמהדר מבצע inline) ללא תקורת קריאה לפונקציה
המלצה: העדיפו inline על פני מאקרו פונקציונלי. השתמשו במאקרו רק כשצריכים יכולות של הפרה-מעבד (כמו # ו-##).

טבלת עזר: טיפוסי נתונים בשפת C

טיפוס גודל (בתים) טווח (signed) Format Specifier הערות
char 1 -128 עד 127 %c / %d משמש גם כתו וגם כמספר שלם קטן
unsigned char 1 0 עד 255 %u בית אחד ללא סימן
short 2 -32,768 עד 32,767 %hd מספר שלם קצר
int 4 -2,147,483,648 עד 2,147,483,647 %d הטיפוס השלם הנפוץ ביותר
unsigned int 4 0 עד 4,294,967,295 %u שלם ללא סימן
long 4–8 תלוי פלטפורמה %ld לפחות 32 ביט. ב-Linux 64-bit: 8 בתים
long long 8 ±9.2 × 10¹⁸ %lld מובטח לפחות 64 ביט
float 4 ±3.4 × 10³⁸ %f דיוק יחיד – כ-7 ספרות משמעותיות
double 8 ±1.8 × 10³⁰⁸ %lf דיוק כפול – כ-15 ספרות משמעותיות
enum 4 תלוי בערכים %d קבועים שלמים בעלי שמות קריאים

טבלת עזר: הוראות פרה-מעבד

הוראה תחביר תיאור רמה
#define (קבוע) #define NAME value מגדיר מאקרו – החלפת טקסט פשוטה בסיסי
#define (פונקציה) #define F(x) ((x)+1) מאקרו עם פרמטרים – מדמה פונקציה בסיסי
#undef #undef NAME מבטל הגדרה של מאקרו קיים בינוני
# (stringize) #define S(x) #x ממיר פרמטר למחרוזת בינוני
## (token paste) #define V(n) var_##n מצמיד שני טוקנים לשם אחד בינוני
#include <> #include <stdio.h> הכללת קובץ כותרת מערכתי בסיסי
#include "" #include "my.h" הכללת קובץ כותרת מקומי בסיסי
#ifdef #ifdef DEBUG תנאי – בודק אם מאקרו מוגדר בסיסי
#ifndef #ifndef HEADER_H תנאי – בודק אם מאקרו לא מוגדר בסיסי
#if #if VERSION > 2 תנאי עם ביטוי מספרי או לוגי בינוני
#elif #elif VERSION == 3 תנאי נוסף בשרשרת (else-if) בינוני
#else #else ענף ברירת מחדל בתנאי בסיסי
#endif #endif סיום בלוק תנאי בסיסי
#error #error "msg" עוצר הידור עם הודעת שגיאה בסיסי
#pragma once #pragma once מונע הכללה כפולה של קובץ בסיסי
#pragma pack #pragma pack(push,1) שליטה ביישור שדות ב-struct בינוני
__FILE__ __FILE__ מאקרו מוגדר מראש – שם הקובץ בינוני
__LINE__ __LINE__ מאקרו מוגדר מראש – מספר שורה בינוני
__DATE__ __DATE__ מאקרו מוגדר מראש – תאריך קומפילציה בינוני
__TIME__ __TIME__ מאקרו מוגדר מראש – שעת קומפילציה בינוני

סיכום

שפת C מציעה מגוון מנגנונים בסיסיים ומתקדמים – מארגון ערכים באמצעות enum, struct ו-union, דרך בקרת זרימה עם switch, ועד שימוש יעיל בפרה-מעבד באמצעות מאקרואים, הוראות תנאי, אופרטורי # ו-##, והכללות נכונות.

נקודות מפתח לזכור:

  • סוגריים במאקרואים – תמיד עטפו פרמטרים בסוגריים כדי למנוע בעיות סדר פעולות.
  • תופעות לוואי – לעולם אל תעבירו ++/-- כפרמטר למאקרו.
  • typedef עדיף על #define להגדרת טיפוסים – בטוח יותר עם מצביעים.
  • inline עדיף על מאקרו פונקציונלי – מאפשר בדיקת טיפוסים ודיבוג.
  • Include Guards או #pragma once – חובה בכל קובץ כותרת.
  • מאקרואים מוגדרים מראש כמו __FILE__ ו-__LINE__ – שימושיים מאוד לדיבוג.

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

תודה! בזכותכם נוכל להשתפר