Functions & Calling Conventions

מבחנים

120 שאלות מעקב קוד מעשיות ביסודות שפת C ופרה-מעבד – קראו קוד וחזו את הפלט. השאלות מכסות מלכודות #define, enum, struct, union, switch fall-through, אריתמטיקה על char/ASCII ופעולות bitwise. מבוסס על סגנון מיון סייבר כהכנה ליחידה 8200 ולתוכנית גאמא סייבר. רמות קושי: קלה, בינונית, קשה.

  • enum
  • struct
  • union
  • switch
  • #define
  • #ifdef / #undef
  • # (stringify)
  • ## (token paste)
  • char / ASCII
  • bitwise ops
  • endianness
  • atoi
טיפ: קראו את המדריך המלא למטה לפני שמתחילים – שימו לב במיוחד למלכודות סוגריים ב-#define ולכללי fall-through ב-switch.

בהצלחה!

תוכן עניינים - מדריך מעקב קוד
חלק א׳: Inline Functions
שימוש בסיסי ב-inline inline עם מצביעים inline עם משתנים סטטיים inline עם רקורסיה קריאות inline משורשרות inline עם מערכים
חלק ב׳: cdecl Calling Convention
cdecl בסיסי cdecl עם רקורסיה cdecl עם משתנים סטטיים cdecl עם מצביעי פונקציות תחביר חוצה-קומפיילרים
חלק ג׳: fastcall Calling Convention
fastcall בסיסי fastcall עם גלישה ל-stack שילוב cdecl ו-fastcall טבלת השוואה: cdecl מול fastcall
חלק ד׳: Function Pointers (הנושא הנבחן ביותר!)
תחביר הכרזה מערכי מצביעי פונקציות Callbacks פונקציה שמחזירה מצביע לפונקציה struct עם שדה מצביע פונקציה רקורסיה דרך מצביע פונקציה טבלת עזר: תחביר מצביעי פונקציות
חלק ה׳: Variadic Functions
va_list בסיסי סינון עם va_arg פונקציות וריאדיות עם double מערך של מצביעים לפונקציות וריאדיות טבלת עזר: זרימת va_list
חלק ו׳: Static Local Variables
דפוס counter מצבר מצטבר Fibonacci generator עם static
חלק ז׳: System Calls
write() עם fd וספירת בתים read() עם buffer fork() ושכפול תהליכים open(), close(), lseek() getpid() ו-getppid()
חלק ח׳: Recursion
factorial, power, fibonacci GCD ו-sumDown רקורסיה משולבת עם inline/cdecl/FP
חלק ט׳: Global Variable Mutation
פונקציות שמשנות globals
חלק י׳: #ifdef _MSC_VER Portability
תחביר חוצה-קומפיילרים סיכום

inline - שימוש בסיסי

פונקציית inline מבקשת מהקומפיילר להטמיע את גוף הפונקציה ישירות במקום הקריאה. בשאלות מעקב קוד, ההתנהגות זהה לפונקציה רגילה - ה-inline משפיע רק על ביצועים, לא על הלוגיקה.

#include <stdio.h>

static inline int square(int x) {
    return x * x;
}

static inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 3;
    int y = square(x);          // 3*3 = 9
    int z = add(y, x);          // 9+3 = 12
    printf("%d %d %d\n", x, y, z);
    return 0;
}
// Output: 3 9 12
כלל: ב-code tracing, inline לא משנה את התוצאה. הקוד עובד בדיוק כמו פונקציה רגילה.

inline עם מצביעים

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

#include <stdio.h>

static inline int doubleIt(int x) {
    return x * 2;
}

int main() {
    int (*fp)(int) = doubleIt;  // fp points to doubleIt
    int a = fp(5);               // calls doubleIt(5) = 10
    int b = doubleIt(5);         // also 10
    printf("%d %d\n", a, b);
    return 0;
}
// Output: 10 10

inline עם משתנים סטטיים (persistence!)

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

#include <stdio.h>

static inline int counter() {
    static int c = 0;    // initialized ONCE, persists!
    c++;
    return c;
}

int main() {
    printf("%d ", counter());  // c: 0→1, prints 1
    printf("%d ", counter());  // c: 1→2, prints 2
    printf("%d\n", counter()); // c: 2→3, prints 3
    return 0;
}
// Output: 1 2 3
מלכודת נפוצה: סטודנטים חושבים ש-inline "מרסט" את המשתנה הסטטי. לא! static פירושו שהמשתנה מאותחל פעם אחת בלבד ושורד בין קריאות, גם ב-inline.

inline עם רקורסיה

הקומפיילר לא יכול באמת לבצע inline לפונקציה רקורסיבית (זה ייצור לולאה אינסופית של הטמעות). בפועל, הקוד רץ כרקורסיה רגילה.

#include <stdio.h>

static inline int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main() {
    printf("%d\n", factorial(5));
    return 0;
}
// Trace: 5*4*3*2*1 = 120
// Output: 120

קריאות inline משורשרות

#include <stdio.h>

static inline int inc(int x) { return x + 1; }
static inline int dbl(int x) { return x * 2; }
static inline int sqr(int x) { return x * x; }

int main() {
    int r = sqr(dbl(inc(2)));
    // Step 1: inc(2)       = 3
    // Step 2: dbl(3)       = 6
    // Step 3: sqr(6)       = 36
    printf("%d\n", r);
    return 0;
}
// Output: 36

inline עם מערכים

#include <stdio.h>

static inline int sumArr(int *arr, int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += arr[i];
    return s;
}

int main() {
    int a[] = {1, 2, 3, 4};
    printf("%d\n", sumArr(a, 4));
    return 0;
}
// Trace: 0+1+2+3+4 = 10
// Output: 10

cdecl - שימוש בסיסי

מוסכמת cdecl היא ברירת המחדל ברוב קומפיילרי C. כל הפרמטרים עוברים דרך ה-stack, הקורא (caller) מנקה את ה-stack. בשאלות קוד, ציון __attribute__((cdecl)) לא משנה את הפלט - הוא רק מדגיש את המוסכמה.

#include <stdio.h>

int __attribute__((cdecl)) add(int a, int b) {
    return a + b;
}

int __attribute__((cdecl)) mul(int a, int b) {
    return a * b;
}

int main() {
    int x = add(3, 4);   // 7
    int y = mul(x, 2);   // 14
    printf("%d %d\n", x, y);
    return 0;
}
// Output: 7 14

cdecl עם רקורסיה - factorial, fib, GCD

#include <stdio.h>

int __attribute__((cdecl)) fact(int n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}

int __attribute__((cdecl)) fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}

int __attribute__((cdecl)) gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

int main() {
    printf("%d\n", fact(5));
    // 5*4*3*2*1 = 120

    printf("%d\n", fib(6));
    // fib(6)=fib(5)+fib(4)=5+3=8

    printf("%d\n", gcd(48, 18));
    // gcd(48,18)→gcd(18,12)→gcd(12,6)→gcd(6,0)→6

    return 0;
}
// Output:
// 120
// 8
// 6

cdecl עם משתנים סטטיים

#include <stdio.h>

int __attribute__((cdecl)) accumulate(int val) {
    static int total = 0;
    total += val;
    return total;
}

int main() {
    printf("%d\n", accumulate(5));   // total: 0+5=5
    printf("%d\n", accumulate(3));   // total: 5+3=8
    printf("%d\n", accumulate(10));  // total: 8+10=18
    return 0;
}
// Output:
// 5
// 8
// 18

cdecl עם מצביעי פונקציות

#include <stdio.h>

int __attribute__((cdecl)) add(int a, int b) { return a + b; }
int __attribute__((cdecl)) sub(int a, int b) { return a - b; }

int main() {
    int (__attribute__((cdecl)) *op)(int, int);

    op = add;
    printf("%d\n", op(10, 3));  // add(10,3) = 13

    op = sub;
    printf("%d\n", op(10, 3));  // sub(10,3) = 7
    return 0;
}
// Output:
// 13
// 7

תחביר חוצה-קומפיילרים (#ifdef _MSC_VER)

#include <stdio.h>

#ifdef _MSC_VER
  #define CDECL __cdecl
#else
  #define CDECL __attribute__((cdecl))
#endif

int CDECL multiply(int a, int b) {
    return a * b;
}

int main() {
    printf("%d\n", multiply(6, 7));
    return 0;
}
// Output: 42 (same on both compilers)

fastcall - שימוש בסיסי

ב-fastcall, שני הפרמטרים הראשונים עוברים ברגיסטרים ECX ו-EDX (בארכיטקטורת 32-bit). בשאלות מעקב קוד, ההתנהגות הלוגית זהה - ההבדל הוא ברמת ה-assembly בלבד.

#include <stdio.h>

#ifdef _MSC_VER
  #define FASTCALL __fastcall
#else
  #define FASTCALL __attribute__((fastcall))
#endif

int FASTCALL add(int a, int b) {
    // a in ECX, b in EDX (32-bit)
    return a + b;
}

int main() {
    printf("%d\n", add(10, 20));
    return 0;
}
// Output: 30

fastcall עם 3-4 פרמטרים (גלישה ל-stack)

כשיש יותר משני פרמטרים, הנוספים עוברים דרך ה-stack כרגיל. הפלט הלוגי לא משתנה.

#include <stdio.h>

#ifdef _MSC_VER
  #define FASTCALL __fastcall
#else
  #define FASTCALL __attribute__((fastcall))
#endif

int FASTCALL sum4(int a, int b, int c, int d) {
    // a→ECX, b→EDX, c→stack, d→stack
    return a + b + c + d;
}

int main() {
    printf("%d\n", sum4(1, 2, 3, 4));
    return 0;
}
// Output: 10

שילוב cdecl ו-fastcall בתוכנית אחת

#include <stdio.h>

#ifdef _MSC_VER
  #define CDECL    __cdecl
  #define FASTCALL __fastcall
#else
  #define CDECL    __attribute__((cdecl))
  #define FASTCALL __attribute__((fastcall))
#endif

int CDECL slow_add(int a, int b) {
    return a + b;  // all params on stack
}

int FASTCALL fast_add(int a, int b) {
    return a + b;  // a in ECX, b in EDX
}

int main() {
    int r1 = slow_add(5, 3);  // 8
    int r2 = fast_add(5, 3);  // 8
    printf("%d %d\n", r1, r2);
    return 0;
}
// Output: 8 8
// Same result! Difference is only in assembly.

טבלת השוואה: cdecl מול fastcall

תכונה cdecl fastcall
העברת פרמטרים הכל דרך ה-stack 2 ראשונים ב-ECX/EDX, שאר ב-stack
ניקוי stack Caller מנקה Callee מנקה
תמיכה ב-variadic כן (הקורא מנקה, יודע כמה דחף) לא (ה-callee לא יודע כמה לנקות)
ביצועים תקני, גישה ל-stack מהיר יותר (רגיסטרים מהירים מ-RAM)
GCC syntax __attribute__((cdecl)) __attribute__((fastcall))
MSVC syntax __cdecl __fastcall
שם מעוטר (mangling) _func @func@N (N=bytes of params)
השפעה על פלט code tracing אין - רק assembly אין - רק assembly

Function Pointers - תחביר הכרזה

זהו הנושא הנבחן ביותר. מצביע לפונקציה מאחסן כתובת של פונקציה ומאפשר לקרוא לה באופן דינמי. התחביר הוא:

// Declaration syntax:
// return_type (*pointer_name)(param_types)

#include <stdio.h>

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int main() {
    int (*fp)(int, int);  // declare FP

    fp = add;
    printf("%d\n", fp(3, 4));   // add(3,4) = 7

    fp = mul;
    printf("%d\n", fp(3, 4));   // mul(3,4) = 12

    printf("%d\n", (*fp)(5, 6)); // also mul(5,6) = 30
    return 0;
}
// Output:
// 7
// 12
// 30

מערכי מצביעי פונקציות

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

int main() {
    int (*ops[3])(int, int) = { add, sub, mul };

    for (int i = 0; i < 3; i++) {
        printf("%d ", ops[i](10, 3));
    }
    printf("\n");
    return 0;
}
// Trace:
// i=0: ops[0](10,3) = add(10,3) = 13
// i=1: ops[1](10,3) = sub(10,3) = 7
// i=2: ops[2](10,3) = mul(10,3) = 30
// Output: 13 7 30

Callbacks - applyTwice, doSteps, process

#include <stdio.h>

int increment(int x) { return x + 1; }
int doubleIt(int x)  { return x * 2; }

int applyTwice(int (*f)(int), int val) {
    return f(f(val));
}

int doSteps(int (**steps)(int), int n, int val) {
    for (int i = 0; i < n; i++)
        val = steps[i](val);
    return val;
}

int main() {
    printf("%d\n", applyTwice(increment, 5));
    // f(5) = 6, f(6) = 7 → 7

    printf("%d\n", applyTwice(doubleIt, 3));
    // f(3) = 6, f(6) = 12 → 12

    int (*pipeline[3])(int) = { increment, doubleIt, increment };
    printf("%d\n", doSteps(pipeline, 3, 4));
    // Step 0: increment(4) = 5
    // Step 1: doubleIt(5)  = 10
    // Step 2: increment(10) = 11 → 11

    return 0;
}
// Output:
// 7
// 12
// 11

פונקציה שמחזירה מצביע לפונקציה

תחביר מורכב אך חשוב מאוד. הפונקציה getOp מחזירה מצביע לפונקציה.

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

// Function that returns a function pointer
// Syntax: return_type (*func_name(params))(fp_params)
int (*getOp(int choice))(int, int) {
    if (choice == 0) return add;
    return sub;
}

int main() {
    int (*op)(int, int);

    op = getOp(0);
    printf("%d\n", op(10, 3));  // add(10,3) = 13

    op = getOp(1);
    printf("%d\n", op(10, 3));  // sub(10,3) = 7

    // Direct chained call:
    printf("%d\n", getOp(0)(4, 5)); // add(4,5) = 9
    return 0;
}
// Output:
// 13
// 7
// 9

struct עם שדה מצביע פונקציה

#include <stdio.h>

typedef struct {
    char *name;
    int (*operate)(int, int);
} Operation;

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int main() {
    Operation ops[2] = {
        { "add", add },
        { "mul", mul }
    };

    for (int i = 0; i < 2; i++) {
        printf("%s(%d,%d) = %d\n",
               ops[i].name, 6, 7,
               ops[i].operate(6, 7));
    }
    return 0;
}
// Output:
// add(6,7) = 13
// mul(6,7) = 42

רקורסיה דרך מצביע פונקציה

#include <stdio.h>

int countdown(int n, int (*self)(int, int (*)(int, int (*)()))) {
    // complex - let's use a simpler pattern:
}

// Simpler approach - static FP array for dispatch:
int base(int n) { return 0; }
int recurse(int n);

int (*dispatch[2])(int) = { base, recurse };

int recurse(int n) {
    printf("%d ", n);
    return dispatch[n > 0](n - 1);
    // if n>0: calls recurse(n-1) = dispatch[1](n-1)
    // if n==0: calls base(n-1)    = dispatch[0](-1) = 0
}

int main() {
    recurse(3);
    printf("\n");
    return 0;
}
// Trace:
// recurse(3): print 3, n>0→dispatch[1](2)=recurse(2)
// recurse(2): print 2, n>0→dispatch[1](1)=recurse(1)
// recurse(1): print 1, n>0→dispatch[1](0)=recurse(0)
// recurse(0): print 0, n==0→dispatch[0](-1)=base(-1)=0
// Output: 3 2 1 0

טבלת עזר: תחביר מצביעי פונקציות

דפוס תחביר דוגמה
מצביע פשוט int (*fp)(int, int) fp = add; fp(3,4);
מערך של FP int (*arr[3])(int, int) arr[0] = add; arr[0](3,4);
FP כפרמטר (callback) void f(int (*cb)(int)) f(increment);
פונקציה מחזירה FP int (*getOp(int))(int,int) getOp(0)(3,4);
typedef ל-FP typedef int (*OpFunc)(int,int) OpFunc fp = add;
FP בתוך struct struct S { int (*f)(int); }; s.f(5);
מצביע ל-מצביע-לפונקציה int (**pp)(int, int) pp = &fp; (*pp)(3,4);

Variadic Functions - va_list בסיסי

פונקציות וריאדיות מקבלות מספר משתנה של ארגומנטים באמצעות stdarg.h. הזרימה: va_startva_arg (חוזר) → va_end.

#include <stdio.h>
#include <stdarg.h>

int sum_all(int count, ...) {
    va_list args;
    va_start(args, count);

    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }
    va_end(args);
    return total;
}

int main() {
    printf("%d\n", sum_all(3, 10, 20, 30));
    printf("%d\n", sum_all(5, 1, 2, 3, 4, 5));
    return 0;
}
// Trace sum_all(3, 10, 20, 30):
//   i=0: va_arg → 10, total=10
//   i=1: va_arg → 20, total=30
//   i=2: va_arg → 30, total=60
// Output:
// 60
// 15

product_all

#include <stdio.h>
#include <stdarg.h>

int product_all(int count, ...) {
    va_list args;
    va_start(args, count);
    int result = 1;
    for (int i = 0; i < count; i++)
        result *= va_arg(args, int);
    va_end(args);
    return result;
}

int main() {
    printf("%d\n", product_all(4, 2, 3, 4, 5));
    return 0;
}
// Trace: 1*2=2, 2*3=6, 6*4=24, 24*5=120
// Output: 120

סינון עם va_arg - sum_even, count_negatives

#include <stdio.h>
#include <stdarg.h>

int sum_even(int count, ...) {
    va_list args;
    va_start(args, count);
    int total = 0;
    for (int i = 0; i < count; i++) {
        int val = va_arg(args, int);
        if (val % 2 == 0) total += val;
    }
    va_end(args);
    return total;
}

int count_negatives(int count, ...) {
    va_list args;
    va_start(args, count);
    int neg = 0;
    for (int i = 0; i < count; i++) {
        if (va_arg(args, int) < 0) neg++;
    }
    va_end(args);
    return neg;
}

int main() {
    printf("%d\n", sum_even(5, 1, 2, 3, 4, 5));
    // 1(odd),2(even→+2),3(odd),4(even→+4),5(odd) = 6

    printf("%d\n", count_negatives(4, -3, 5, -1, 0));
    // -3(neg),5(no),-1(neg),0(no) = 2

    return 0;
}
// Output:
// 6
// 2

פונקציות וריאדיות עם double

#include <stdio.h>
#include <stdarg.h>

double average(int count, ...) {
    va_list args;
    va_start(args, count);
    double sum = 0.0;
    for (int i = 0; i < count; i++)
        sum += va_arg(args, double);
    va_end(args);
    return sum / count;
}

int main() {
    printf("%.1f\n", average(3, 2.0, 4.0, 6.0));
    return 0;
}
// Trace: (2.0+4.0+6.0)/3 = 12.0/3 = 4.0
// Output: 4.0
מלכודת: בפונקציות וריאדיות, float מקודם ל-double אוטומטית. לכן חייבים לציין va_arg(args, double) ולא va_arg(args, float).

מערך של מצביעים לפונקציות וריאדיות

#include <stdio.h>
#include <stdarg.h>

int sum_all(int count, ...) {
    va_list a; va_start(a, count);
    int s = 0;
    for (int i = 0; i < count; i++) s += va_arg(a, int);
    va_end(a);
    return s;
}

int product_all(int count, ...) {
    va_list a; va_start(a, count);
    int p = 1;
    for (int i = 0; i < count; i++) p *= va_arg(a, int);
    va_end(a);
    return p;
}

typedef int (*VarFunc)(int, ...);

int main() {
    VarFunc funcs[2] = { sum_all, product_all };

    printf("%d\n", funcs[0](3, 2, 3, 4));  // sum: 2+3+4=9
    printf("%d\n", funcs[1](3, 2, 3, 4));  // prod: 2*3*4=24
    return 0;
}
// Output:
// 9
// 24

טבלת עזר: זרימת va_list

שלב פונקציה/מאקרו תיאור
1 va_list args; הכרזה על משתנה שיחזיק את רשימת הארגומנטים
2 va_start(args, last_fixed) אתחול - last_fixed הוא הפרמטר הקבוע האחרון
3 va_arg(args, type) שליפת הארגומנט הבא מהרשימה לפי הטיפוס שצוין
4 va_end(args) ניקוי - חובה לקרוא לפני יציאה מהפונקציה
כלל חשוב: va_arg לא יודע מה הטיפוס האמיתי. אם תציינו טיפוס שגוי (למשל float במקום double), תקבלו ערך שגוי ללא שגיאת קומפילציה.

Static Local Variables - דפוס counter

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

#include <stdio.h>

int counter() {
    static int n = 0;
    n++;
    return n;
}

int main() {
    printf("%d ", counter());   // n: 0→1, return 1
    printf("%d ", counter());   // n: 1→2, return 2
    printf("%d ", counter());   // n: 2→3, return 3
    printf("%d\n", counter()); // n: 3→4, return 4
    return 0;
}
// Output: 1 2 3 4

מצבר מצטבר (accumulator)

#include <stdio.h>

int addTo(int x) {
    static int sum = 0;
    sum += x;
    return sum;
}

int main() {
    printf("%d\n", addTo(10));  // sum: 0+10=10
    printf("%d\n", addTo(5));   // sum: 10+5=15
    printf("%d\n", addTo(20));  // sum: 15+20=35
    printf("%d\n", addTo(-5));  // sum: 35+(-5)=30
    return 0;
}
// Output:
// 10
// 15
// 35
// 30

Fibonacci generator עם static

#include <stdio.h>

int next_fib() {
    static int a = 0, b = 1;
    int result = a;
    int temp = a + b;
    a = b;
    b = temp;
    return result;
}

int main() {
    for (int i = 0; i < 8; i++)
        printf("%d ", next_fib());
    printf("\n");
    return 0;
}
// Trace (a,b → result):
// Call 1: a=0,b=1 → result=0, temp=1, a=1,b=1
// Call 2: a=1,b=1 → result=1, temp=2, a=1,b=2
// Call 3: a=1,b=2 → result=1, temp=3, a=2,b=3
// Call 4: a=2,b=3 → result=2, temp=5, a=3,b=5
// Call 5: a=3,b=5 → result=3, temp=8, a=5,b=8
// Call 6: a=5,b=8 → result=5, temp=13,a=8,b=13
// Call 7: a=8,b=13→ result=8, temp=21,a=13,b=21
// Call 8: a=13,b=21→result=13,temp=34,a=21,b=34
// Output: 0 1 1 2 3 5 8 13

System Calls - write() עם fd וספירת בתים

הפונקציה write(fd, buf, count) כותבת count בתים מ-buf ל-file descriptor fd. fd=1 הוא stdout, fd=2 הוא stderr.

#include <unistd.h>

int main() {
    write(1, "Hello\n", 6);
    // fd=1 (stdout), writes 6 bytes: H,e,l,l,o,\n

    write(1, "ABCDEF", 3);
    // writes only 3 bytes: A,B,C

    write(1, "\n", 1);
    return 0;
}
// Output:
// Hello
// ABC
מלכודת נפוצה: write(1, "Hello", 3) ידפיס רק Hel - לא את כל המחרוזת! הפרמטר השלישי קובע כמה בתים נכתבים.

read() עם buffer

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

int main() {
    int fd = open("test.txt", O_RDONLY);
    // Assume test.txt contains: "ABCDEFGHIJ"

    char buf[5];
    int n = read(fd, buf, 4);
    // reads 4 bytes into buf: 'A','B','C','D'
    buf[n] = '\0';  // null-terminate
    printf("Read %d bytes: %s\n", n, buf);

    close(fd);
    return 0;
}
// Output: Read 4 bytes: ABCD

fork() ושכפול תהליכים

fork() הוא נושא מרכזי במבחנים. fork() יוצר עותק של התהליך. ההורה מקבל את ה-PID של הילד, הילד מקבל 0. כל fork() מכפיל את מספר התהליכים.

#include <stdio.h>
#include <unistd.h>

int main() {
    fork();  // Now 2 processes
    printf("A\n");
    return 0;
}
// Output: A is printed 2 times
// A
// A
#include <stdio.h>
#include <unistd.h>

int main() {
    fork();  // 1 → 2 processes
    fork();  // 2 → 4 processes
    printf("B\n");
    return 0;
}
// Output: B is printed 4 times (2^2 = 4)
// B
// B
// B
// B
#include <stdio.h>
#include <unistd.h>

int main() {
    fork();  // 1 → 2
    fork();  // 2 → 4
    fork();  // 4 → 8
    printf("C\n");
    return 0;
}
// Output: C is printed 8 times (2^3 = 8)
כלל: N קריאות fork() רצופות יוצרות 2^N תהליכים. כל תהליך ממשיך מנקודת ה-fork ואילך.

fork() עם תנאי

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // Child process
        printf("Child\n");
    } else {
        // Parent process
        printf("Parent\n");
    }
    printf("Both\n");
    return 0;
}
// Output (order may vary):
// Parent
// Both
// Child
// Both
// (Parent prints "Parent" + "Both", Child prints "Child" + "Both")

open(), close(), lseek()

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    // Assume file contains: "ABCDEFGHIJ" (10 bytes)
    int fd = open("data.txt", O_RDONLY);

    char buf[4];
    read(fd, buf, 3);      // reads "ABC", pos=3
    buf[3] = '\0';
    printf("%s\n", buf);    // ABC

    lseek(fd, 5, SEEK_SET); // move to position 5
    read(fd, buf, 3);      // reads "FGH", pos=8
    buf[3] = '\0';
    printf("%s\n", buf);    // FGH

    lseek(fd, -2, SEEK_CUR); // move back 2 from current (8-2=6)
    read(fd, buf, 2);       // reads "GH", pos=8
    buf[2] = '\0';
    printf("%s\n", buf);     // GH

    close(fd);
    return 0;
}
// Output:
// ABC
// FGH
// GH

getpid() ו-getppid()

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Before fork: PID=%d\n", getpid());

    pid_t pid = fork();
    if (pid == 0) {
        printf("Child: PID=%d, Parent PID=%d\n",
               getpid(), getppid());
    } else {
        printf("Parent: PID=%d, Child PID=%d\n",
               getpid(), pid);
    }
    return 0;
}
// Output (PIDs are system-assigned, example):
// Before fork: PID=1234
// Parent: PID=1234, Child PID=1235
// Child: PID=1235, Parent PID=1234
שימו לב: getpid() מחזיר את ה-PID של התהליך הנוכחי. getppid() מחזיר את ה-PID של ההורה. בשאלות מבחן בדרך כלל שואלים על מספר ההדפסות, לא על ערכי PID ספציפיים.

Recursion - factorial, power, fibonacci

#include <stdio.h>

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int power(int base, int exp) {
    if (exp == 0) return 1;
    return base * power(base, exp - 1);
}

int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

int main() {
    printf("%d\n", factorial(6));  // 720
    printf("%d\n", power(2, 8));    // 256
    printf("%d\n", fib(7));        // 13
    return 0;
}
// Trace factorial(6): 6*5*4*3*2*1 = 720
// Trace power(2,8): 2*2*2*2*2*2*2*2 = 256
// Trace fib(7): fib(6)+fib(5) = 8+5 = 13
// Output:
// 720
// 256
// 13

GCD ו-sumDown

#include <stdio.h>

int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

int sumDown(int n) {
    if (n <= 0) return 0;
    return n + sumDown(n - 1);
}

int main() {
    printf("%d\n", gcd(54, 24));
    // gcd(54,24)→gcd(24,6)→gcd(6,0)→6

    printf("%d\n", sumDown(5));
    // 5+4+3+2+1+0 = 15

    printf("%d\n", gcd(100, 75));
    // gcd(100,75)→gcd(75,25)→gcd(25,0)→25
    return 0;
}
// Output:
// 6
// 15
// 25

רקורסיה משולבת עם inline, cdecl ומצביעי פונקציות

#include <stdio.h>

static inline int mul2(int x) { return x * 2; }

int __attribute__((cdecl)) recSum(int n) {
    if (n <= 0) return 0;
    return n + recSum(n - 1);
}

int main() {
    int (*fp)(int) = mul2;
    int val = recSum(4);     // 4+3+2+1+0 = 10
    int result = fp(val);     // mul2(10) = 20
    printf("%d\n", result);
    return 0;
}
// Output: 20
#include <stdio.h>

int applyRec(int (*f)(int), int val, int times) {
    if (times <= 0) return val;
    return applyRec(f, f(val), times - 1);
}

int inc(int x) { return x + 1; }
int dbl(int x) { return x * 2; }

int main() {
    printf("%d\n", applyRec(inc, 0, 5));
    // inc applied 5 times to 0: 0→1→2→3→4→5

    printf("%d\n", applyRec(dbl, 1, 4));
    // dbl applied 4 times to 1: 1→2→4→8→16
    return 0;
}
// Output:
// 5
// 16

Global Variable Mutation - פונקציות שמשנות globals

#include <stdio.h>

int g = 10;

void addToG(int x) { g += x; }
void mulG(int x)   { g *= x; }
void resetG()       { g = 0; }

int main() {
    printf("%d\n", g);    // 10
    addToG(5);
    printf("%d\n", g);    // 10+5 = 15
    mulG(3);
    printf("%d\n", g);    // 15*3 = 45
    addToG(-10);
    printf("%d\n", g);    // 45+(-10) = 35
    resetG();
    printf("%d\n", g);    // 0
    return 0;
}
// Output:
// 10
// 15
// 45
// 35
// 0

שילוב globals עם מצביעי פונקציות

#include <stdio.h>

int val = 1;

void doubleVal() { val *= 2; }
void addTen()    { val += 10; }
void subThree()  { val -= 3; }

int main() {
    void (*steps[4])() = { doubleVal, addTen, doubleVal, subThree };

    for (int i = 0; i < 4; i++) {
        steps[i]();
        printf("val=%d\n", val);
    }
    return 0;
}
// Trace:
// step 0: doubleVal() → val = 1*2 = 2
// step 1: addTen()    → val = 2+10 = 12
// step 2: doubleVal() → val = 12*2 = 24
// step 3: subThree()  → val = 24-3 = 21
// Output:
// val=2
// val=12
// val=24
// val=21

#ifdef _MSC_VER - תחביר חוצה-קומפיילרים

MSVC (Visual Studio) ו-GCC משתמשים בתחביר שונה עבור calling conventions. הפתרון: #ifdef _MSC_VER מזהה את MSVC, וה-#else מטפל ב-GCC/Clang.

#include <stdio.h>

#ifdef _MSC_VER
    #define CDECL    __cdecl
    #define FASTCALL __fastcall
#else
    #define CDECL    __attribute__((cdecl))
    #define FASTCALL __attribute__((fastcall))
#endif

int CDECL addC(int a, int b)    { return a + b; }
int FASTCALL addF(int a, int b) { return a + b; }

int main() {
    printf("cdecl:    %d\n", addC(10, 20));
    printf("fastcall: %d\n", addF(10, 20));
    return 0;
}
// Output (identical on both compilers):
// cdecl:    30
// fastcall: 30
בשאלות מעקב קוד: ה-#ifdef קובע איזה ענף ייכלל. אם השאלה מציינת "compiled with GCC", אז _MSC_VER לא מוגדר וה-#else הוא הרלוונטי. הפלט הלוגי זהה בשני המקרים.

דוגמה מתקדמת - בחירת פונקציה לפי קומפיילר

#include <stdio.h>

#ifdef _MSC_VER
    #define CALL_CONV __cdecl
    #define COMPILER_NAME "MSVC"
#elif defined(__GNUC__)
    #define CALL_CONV __attribute__((cdecl))
    #define COMPILER_NAME "GCC"
#else
    #define CALL_CONV
    #define COMPILER_NAME "Unknown"
#endif

int CALL_CONV compute(int x) {
    return x * x + 1;
}

int main() {
    printf("Compiler: %s\n", COMPILER_NAME);
    printf("Result: %d\n", compute(7));
    return 0;
}
// If compiled with GCC:
// Output:
// Compiler: GCC
// Result: 50

סיכום

מדריך זה כיסה את כל הנושאים הנבחנים ב-120 שאלות הקוד בנושא Functions & Calling Conventions. נקודות מפתח למעקב קוד:

  • inline - לא משנה את הפלט הלוגי. משתנים static בתוך inline שורדים בין קריאות.
  • cdecl / fastcall - בשאלות מעקב קוד, ההבדל הוא ברמת ה-assembly בלבד. הפלט זהה.
  • Function Pointers - עקבו אחרי לאיזו פונקציה המצביע מפנה בכל רגע. שימו לב למערכים, callbacks, ופונקציות שמחזירות FP.
  • Variadic Functions - va_arg שולף ארגומנטים בסדר. float מקודם ל-double.
  • Static locals - מאותחלים פעם אחת. רשמו טבלת ערכים לכל קריאה.
  • fork() - N קריאות = 2^N תהליכים. ציירו עץ תהליכים.
  • write() - הפרמטר השלישי קובע כמה בתים נכתבים, לא אורך המחרוזת.
  • Recursion - עקבו אחרי ה-call stack. שימו לב לתנאי העצירה (base case).
  • Globals - פונקציות יכולות לשנות globals. עקבו אחרי הערך הנוכחי.
  • #ifdef _MSC_VER - זהו את הקומפיילר כדי לדעת איזה ענף ייכלל.
טיפ אחרון: בכל שאלת מעקב קוד, רשמו על דף את ערכי המשתנים לאחר כל שורה. אל תסמכו על הראש בלבד - שאלות עם משתנים סטטיים, globals, רקורסיה ו-fork דורשות מעקב מסודר. הכנה מדויקת ברמה הזו היא שמבדילה את מי שעובר בהצלחה את המיונים של גאמא סייבר ויחידה 8200.
תודה! בזכותכם נוכל להשתפר