Table of Contents

1. Call by Value
#

When you pass a variable to a function by value, C copies the value into a brand-new local variable. The original is never touched.

void calibrate(int value) {
    value = value + 10;        // modifies the LOCAL copy only
}

int main(void) {
    int temperature = 25;
    calibrate(temperature);     // passes a COPY of 25
    printf("%d\n", temperature); // 25  -- unchanged!
}

What happens in memory
#

 ┌──────────────────────────────────────────────────────────┐
 │  main() stack frame            calibrate() stack frame   │
 │                                                          │
 │  temperature                   value                     │
 │  ┌──────────┐    copy 25 →    ┌──────────┐              │
 │  │    25    │ ──────────────► │    25    │              │
 │  └──────────┘                 └──────────┘              │
 │       │                            │                     │
 │       │                       value = value + 10         │
 │       │                            │                     │
 │       ▼                            ▼                     │
 │  ┌──────────┐                 ┌──────────┐              │
 │  │    25    │                 │    35    │              │
 │  └──────────┘                 └──────────┘              │
 │   (untouched)              (destroyed on return)         │
 └──────────────────────────────────────────────────────────┘

Key point: the function receives its own independent copy. Changing value inside calibrate has zero effect on temperature in main.


2. Call by Reference (Pass by Address)
#

To let a function modify the original variable, pass its address using a pointer.

void calibrate(int *value) {
    *value = *value + 10;      // dereferences the pointer → writes to the original
}

int main(void) {
    int temperature = 25;
    calibrate(&temperature);    // passes the ADDRESS of temperature
    printf("%d\n", temperature); // 35  -- changed!
}

What happens in memory
#

 ┌──────────────────────────────────────────────────────────┐
 │  main() stack frame            calibrate() stack frame   │
 │                                                          │
 │  temperature                   value (pointer)           │
 │  addr: 0x1000                  ┌──────────┐              │
 │  ┌──────────┐                 │  0x1000  │──┐           │
 │  │    25    │                 └──────────┘  │           │
 │  └──────────┘                               │           │
 │       ▲                                     │           │
 │       │              *value = 35            │           │
 │       └─────────────────────────────────────┘           │
 │                                                          │
 │  ┌──────────┐                                            │
 │  │    35    │  ← the original is modified!               │
 │  └──────────┘                                            │
 └──────────────────────────────────────────────────────────┘

Side-by-side comparison
#

  Call by VALUE                    Call by REFERENCE (address)
 ─────────────────                ─────────────────────────────
  void f(int val)                  void f(int *val)

  ┌─────┐  copy  ┌─────┐         ┌─────┐  addr   ┌─────────┐
  │  25 │ ────► │  25 │         │  25 │ ◄────── │ &(0x1000)│
  └─────┘        └─────┘         └─────┘         └─────────┘
  original       local copy       original        pointer to it

  original: 25 (safe)             original: 35 (modified via *)

3. The static Keyword: Controlling Visibility
#

static limits the scope of a variable or function to the current file only. Nothing outside the file can see it.

// sensor.c
static int var = 0;                // only accessible inside sensor.c
static void private_func(void) {}  // only callable inside sensor.c

Visibility diagram
#

 ┌─────────── sensor.c ───────────┐    ┌─────────── main.c ──────────┐
 │                                 │    │                              │
 │  static int var = 0;      OK   │    │  extern int var;      ERROR  │
 │  static void private_func();   │    │  private_func();      ERROR  │
 │                                 │    │                              │
 │  int public_var = 0;      OK   │    │  extern int public_var;  OK  │
 │  void public_func();      OK   │    │  public_func();          OK  │
 │                                 │    │                              │
 └─────────────────────────────────┘    └──────────────────────────────┘

              │                                      │
              ▼                                      ▼
       static = file-private                 non-static = globally visible

4. C Memory Layout: Where Everything Lives
#

Every C program’s memory is divided into distinct regions. Here is where each type of variable and function is stored:

 ┌───────────────────────────────────────────────────────────────┐
 │                    C PROGRAM MEMORY MAP                       │
 ├───────────────────────────────────────────────────────────────┤
 │                                                               │
 │  ┌─────────────────────────────────────────────────────────┐  │
 │  │  TEXT (Code) Segment                      [Read-Only]   │  │
 │  │                                                         │  │
 │  │  • Function code: main(), calibrate(), printf()         │  │
 │  │  • String literals: "Hello, World!"                     │  │
 │  │  • const variables (sometimes)                          │  │
 │  └─────────────────────────────────────────────────────────┘  │
 │                                                               │
 │  ┌─────────────────────────────────────────────────────────┐  │
 │  │  DATA Segment                                           │  │
 │  │                                                         │  │
 │  │  Initialized:                                           │  │
 │  │  • int global_var = 10;          (global)               │  │
 │  │  • static int count = 5;         (static)               │  │
 │  │                                                         │  │
 │  │  Uninitialized (BSS):                                   │  │
 │  │  • int global_var;               (defaults to 0)        │  │
 │  │  • static int count;             (defaults to 0)        │  │
 │  └─────────────────────────────────────────────────────────┘  │
 │                                                               │
 │  ┌─────────────────────────────────────────────────────────┐  │
 │  │  HEAP                                  grows ↓          │  │
 │  │                                                         │  │
 │  │  • int *p = malloc(sizeof(int));                        │  │
 │  │  • char *str = calloc(100, 1);                          │  │
 │  │  • Must be freed manually: free(p);                     │  │
 │  │                                                         │  │
 │  │                        ↓ ↓ ↓                            │  │
 │  │                    (grows down)                          │  │
 │  │                                                         │  │
 │  │                    (grows up)                            │  │
 │  │                        ↑ ↑ ↑                            │  │
 │  │                                                         │  │
 │  │  STACK                                 grows ↑          │  │
 │  │                                                         │  │
 │  │  • Local variables: int temperature = 25;               │  │
 │  │  • Function parameters: int value (copy)                │  │
 │  │  • Return addresses                                     │  │
 │  └─────────────────────────────────────────────────────────┘  │
 │                                                               │
 └───────────────────────────────────────────────────────────────┘

Mapping our examples to memory
#

int global_count = 0;              // DATA segment (initialized)
static int file_count = 0;        // DATA segment (static, initialized)

void calibrate(int value) {        // TEXT segment (function code)
    value = value + 10;            // STACK (local parameter)
}

int main(void) {                   // TEXT segment (function code)
    int temperature = 25;          // STACK (local variable)
    int *p = malloc(sizeof(int));  // p on STACK, *p on HEAP
    calibrate(temperature);
    free(p);
}
  Code                    Where in memory?
 ──────────────────────  ─────────────────────────────────
  calibrate() code        TEXT    ← function instructions
  main() code             TEXT    ← function instructions
  "Hello, World!"         TEXT    ← string literal

  global_count = 0        DATA    ← global, initialized
  file_count = 0          DATA    ← static, initialized

  *p (malloc'd data)      HEAP    ← dynamically allocated

  temperature = 25        STACK   ← local variable in main()
  value = 25              STACK   ← parameter copy in calibrate()
  p (the pointer itself)  STACK   ← local variable in main()

Lifetime comparison
#

RegionCreatedDestroyedExample
TextProgram startProgram endcalibrate(), main()
DataProgram startProgram endglobal_count, static int
Heapmalloc() callfree() call*p
StackFunction callFunction returntemperature, value

5. #define and Macros
#

5.1 #define = Text Substitution
#

#define is not a variable. Before the compiler ever sees your code, the preprocessor replaces every occurrence with the literal text you defined.

#define MAX_SENSORS    16
#define TEMP_SENSOR_ID 0x01
 ┌──── Your source code ────┐        ┌─── After preprocessing ───┐
 │                           │        │                            │
 │  int count = MAX_SENSORS; │  ───►  │  int count = 16;          │
 │                           │        │                            │
 │  if (id == TEMP_SENSOR_ID)│  ───►  │  if (id == 0x01)          │
 │                           │        │                            │
 └───────────────────────────┘        └────────────────────────────┘
         you write this                    compiler sees this

5.2 Conditional Compilation
#

Entire blocks of code can be included or excluded at compile time based on defined symbols.

#define DEBUG_MODE 1

#if DEBUG_MODE
    printf("Temperature: %d\n", temp);   // included when DEBUG_MODE is 1
#endif
                    ┌──────────────────┐
                    │  DEBUG_MODE = 1? │
                    └────────┬─────────┘
                       yes / \ no
                          /   \
           ┌─────────────┐     ┌──────────────────┐
           │  printf()   │     │  (code removed   │
           │  compiled   │     │   entirely)       │
           └─────────────┘     └──────────────────┘

You can also switch between configurations:

#ifdef USE_CELSIUS
    #define TEMP_UNIT "C"
#else
    #define TEMP_UNIT "F"
#endif
   ┌──────────────────────────────────────────────────────┐
   │                 Compile with flag?                    │
   │                                                      │
   │    gcc -DUSE_CELSIUS main.c        gcc main.c        │
   │          │                              │             │
   │          ▼                              ▼             │
   │    TEMP_UNIT = "C"              TEMP_UNIT = "F"       │
   └──────────────────────────────────────────────────────┘

5.3 Header Guards
#

When multiple files #include the same header, its contents could be inserted more than once, causing duplicate definition errors. Header guards prevent this.

// sensor.h
#ifndef SENSOR_H        // if SENSOR_H is NOT yet defined...
#define SENSOR_H        // ...define it now (so next #include skips)

typedef struct {
    int id;
    float value;
} Sensor;

void sensor_init(Sensor *s);

#endif                  // end of guard

How the guard works across multiple includes
#

 ┌──── First #include "sensor.h" ────┐
 │                                    │
 │  #ifndef SENSOR_H  →  true        │
 │  #define SENSOR_H                  │
 │  ... contents included ...         │
 │  #endif                            │
 └────────────────────────────────────┘

 ┌──── Second #include "sensor.h" ───┐
 │                                    │
 │  #ifndef SENSOR_H  →  false       │
 │  (SENSOR_H already defined)        │
 │  ... entire file SKIPPED ...       │
 │  #endif                            │
 └────────────────────────────────────┘
  Without header guard:              With header guard:
 ┌──────────────────────┐          ┌──────────────────────┐
 │  main.c              │          │  main.c              │
 │  ┌────────────────┐  │          │  ┌────────────────┐  │
 │  │ #include "s.h" │──┼─► copy   │  │ #include "s.h" │──┼─► copy
 │  └────────────────┘  │          │  └────────────────┘  │
 │  ┌────────────────┐  │          │  ┌────────────────┐  │
 │  │ #include "s.h" │──┼─► copy   │  │ #include "s.h" │──┼─► SKIPPED
 │  └────────────────┘  │          │  └────────────────┘  │
 │                      │          │                      │
 │  ERROR: duplicate!   │          │  OK: only one copy   │
 └──────────────────────┘          └──────────────────────┘

6. Summary
#

ConceptMechanismKey Takeaway
Call by valueCopies the valueOriginal is safe, function works on a copy
Call by referencePasses the address (&)Function can modify the original via *
staticLimits linkage to current fileHides internal details from other files
Memory layoutText, Data, Heap, StackKnow where each variable lives and when it dies
#defineText substitution before compileNot a variable – just find-and-replace
Conditional compilation#if / #ifdef / #ifndefInclude or exclude code at compile time
Header guards#ifndef + #define patternPrevents duplicate inclusion of headers