Skip to main content

Pointers

1. Pointer Operators

1. Address-of Operator (&)

  • The & operator is used to get the memory address of a variable.
int a = 10;
int *p;

p = &a; // p stores the address of a
  • &a means address of variable a.
  • The pointer p now holds that address.

2. Dereference Operator (*)

  • The * operator is used to access the value stored at the address held by a pointer.
int a = 10;
int *p = &a;

printf("%d", *p); // prints 10
  • *p means value stored at the address inside p.
OperatorMeaning
&Address of variable
*Value at address (dereferencing)

2. Pointer Arithmetic

Pointer arithmetic allows pointers to move through memory locations.

If a pointer points to a variable, we can perform arithmetic operations on it.

1. Increment (p++)

Moves pointer to the next memory location of its type.

int arr[3] = {10,20,30};
int *p = arr;

p++; // moves to next integer

If p points to arr[0], after p++ it points to arr[1].

Note: It moves by size of data type.

  • int pointer → moves 4 bytes (usually)
  • char pointer → moves 1 byte

2. Decrement (p--)

Moves pointer to the previous memory location.

p--;

3. Addition (p + n)

Moves pointer forward by n elements.

p = p + 2;

If p points to arr[0], now it points to arr[2].

4. Subtraction (p - n)

Moves pointer backward by n elements.

p = p - 1;

5. Pointer Difference

We can subtract two pointers pointing to the same array.

int *p1 = &arr[2];
int *p2 = &arr[0];

int diff = p1 - p2; // result = 2

This tells the number of elements between the pointers.

3. Important Notes

  • Pointer arithmetic depends on data type size.
  • Arithmetic is usually used with arrays.
  • You cannot add two pointers, only subtract them.
#include <stdio.h>

int main() {
int arr[3] = {10,20,30};
int *p = arr;

printf("%d\n", *p); // 10
p++;
printf("%d\n", *p); // 20
p++;
printf("%d\n", *p); // 30

return 0;
}

In short:

  • & → gets address
  • * → accesses value
  • p++, p--, p+n, p-n → move pointer in memory

Function Calling

Call by Value

  • Definition: In call by value, a copy of the actual argument is passed to the function.
  • Effect: Changes made inside the function do not affect the original variable.
  • When to use: When you don’t want the original data to be modified.
void increment(int x) {
x = x + 1; // changes local copy only
}

int main() {
int num = 5;
increment(num);
printf("num = %d\n", num); // Output: num = 5
return 0;
}

Here, num remains 5 because the function only modified a copy of num.

Call by Reference

  • Definition: In call by reference, the address of the actual argument is passed to the function.
  • Effect: Changes made inside the function directly affect the original variable.
  • When to use: When you want the function to modify the original variable, or when passing large data structures to avoid copying overhead.
void increment(int *x) {
*x = *x + 1; // modifies the value at the address
}

int main() {
int num = 5;
increment(&num); // pass address of num
printf("num = %d\n", num); // Output: num = 6
return 0;
}

Here, num becomes 6 because the function modified the value at its memory address.

Key Differences

FeatureCall by ValueCall by Reference
What is passedCopy of the variableAddress of the variable
Original variable changeNot affectedAffected
Memory overheadHigher for large dataLower for large data
SafetySafer (cannot accidentally modify)Risky if function modifies unintentionally

Function Pointer

A function pointer is a pointer that stores the address of a function instead of a regular variable. This allows you to call functions indirectly, pass functions as arguments, or implement callback mechanisms.

Think of it as a “remote control” that can point to different functions.

return_type (*pointer_name)(parameter_types);
  • return_type → the type of value the function returns
  • pointer_name → the name of the pointer
  • parameter_types → the types of parameters the function takes
// A simple function
int add(int a, int b) {
return a + b;
}

int main() {
// Declare a function pointer
int (*func_ptr)(int, int);

// Assign the function's address to the pointer
func_ptr = &add;

// Call the function using the pointer
int result = func_ptr(5, 3); // or (*func_ptr)(5, 3)
printf("Result: %d\n", result); // Output: Result: 8

return 0;
}
  • You can use func_ptr(5,3) directly; the * is optional.
  • The function pointer must match the signature (return type + parameters) of the function it points to.
  • int *func_ptr(int, int); return an integer pointer.

Uses of Function Pointers

  1. Callback functions – Pass a function as a parameter to another function.
  2. Dynamic function calls – Choose a function to execute at runtime.
  3. Implementing tables of functions – Useful in things like menu systems or handling events.

Example: Using Function Pointer as Callback

#include <stdio.h>

// Calculator functions
void add(int a, int b) {
printf("Addition: %d\n", a + b);
}

void subtract(int a, int b) {
printf("Subtraction: %d\n", a - b);
}

void multiply(int a, int b) {
printf("Multiplication: %d\n", a * b);
}

void divide(int a, int b) {
if (b != 0)
printf("Division: %d\n", a / b);
else
printf("Cannot divide by zero\n");
}

// Function that accepts a function pointer
void execute(void (*operation)(int, int), int x, int y) {
operation(x, y); // Call the passed function
}

int main() {
execute(add, 10, 5);
execute(subtract, 10, 5);
execute(multiply, 10, 5);
execute(divide, 10, 5);

return 0;
}
  • operation is a function pointer.
  • It can point to any function that takes two int arguments and returns void.
  • This allows execute() to act like a callback handler for different calculator operations.

Dangling Pointer

A dangling pointer is a pointer in programming that points to a memory location that has already been freed or is no longer valid. Accessing it can cause undefined behavior, crashes, or security vulnerabilities.

A pointer should point to valid memory. If that memory is deleted, deallocated, or goes out of scope, but the pointer still holds the old address, it becomes a dangling pointer.

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

int main() {
int *ptr = (int*) malloc(sizeof(int));
*ptr = 10;

free(ptr); // memory is released

printf("%d", *ptr); // ❌ ptr is now a dangling pointer

return 0;
}

What Happens

  1. ptr gets memory using malloc.
  2. free(ptr) releases that memory.
  3. ptr still holds the old address, but the memory is no longer owned by the program (ptr have the address, not the value it was holded).
  4. Accessing *ptr may cause garbage values or a crash.

It occur in 3 places

  1. after free
  2. local pointer of function scope
  3. outer scope

Another Common Case (Scope End)

int* func() {
int x = 5;
return &x; // ❌ returning address of local variable
}

x is destroyed when the function ends, so the returned pointer becomes dangling.

Problems Caused by Dangling Pointers

  • Program crashes
  • Unexpected values
  • Memory corruption
  • Security vulnerabilities (exploits)

How to Avoid Dangling Pointers

Set pointer to NULL after freeing

free(ptr);
ptr = NULL;
  • Avoid returning addresses of local variables
  • Use smart pointers in modern C++ (unique_ptr, shared_ptr)
  • Careful memory management
  • Further uses return segmentation fault

A dangling pointer is a pointer that references memory that is no longer valid.

int *ptr = (int*) malloc(sizeof(int));
*ptr = 10;

printf("Pointer address: %p\n", (void*)ptr); // print address
printf("Pointer value: %d\n", *ptr); // print value

free(ptr); // memory is released

// Now safe: ptr is NULL, do NOT dereference it
printf("Pointer after free: %p\n", (void*)ptr);
printf("================\n");

// Reallocate
ptr = (int*) malloc(sizeof(int));
*ptr = 20;

printf("Pointer address: %p\n", (void*)ptr);
printf("Pointer value: %d\n", *ptr);

free(ptr);
ptr = NULL; // avoid dangling pointer

printf("Pointer after second free: %p\n", (void*)ptr);
// printf("%d\n", *ptr); <- ❌ never dereference NULL

static vs malloc

Featurestaticmalloc
Allocation timeCompile/startupRuntime
Memory locationData segmentHeap
SizeFixedFlexible
LifetimeEntire programUntil free()
ManagementAutomaticManual
SpeedFasterSlightly slower

Memory Management

Memory Layout in C

C programs typically have four main memory segments:

  1. Code/Text Segment

    • Stores compiled program instructions.
    • Read-only, fixed size.
  2. Data Segment

    • Stores global and static variables.
    • Divided into:
      • Initialized data segment (e.g., static int x = 5;)
      • Uninitialized data segment / BSS (e.g., static int x;)
  3. Stack

    • Stores local variables and function call information (return addresses).
    • LIFO structure; memory freed automatically when function ends.
  4. Heap

    • Stores dynamically allocated memory (via malloc, calloc, realloc).
    • Must be manually freed using free().
+--------------------+
| Code / Text Segment |
+--------------------+
| Initialized Data |
| Uninitialized Data |
+--------------------+
| Heap | <- grows upward
+--------------------+
| Stack | <- grows downward
+--------------------+

static Keyword in C

The static keyword can be used with variables and functions, and its behavior depends on the context.

Static Variables

  • Inside a function: Retains its value between function calls.
  • Outside a function (global): Limits the scope to the file (internal linkage).
  • The size of the static variable must be define in compile time.

Example (Inside Function):

#include <stdio.h>

void counter() {
static int count = 0; // Initialized only once
count++;
printf("Count: %d\n", count);
}

int main() {
counter(); // Count: 1
counter(); // Count: 2
counter(); // Count: 3
return 0;
}

Example (Outside Function / Global):

static int num = 10; // Accessible only in this file

Static Functions

  • Limits the function scope to the file.
  • Useful for encapsulation in multi-file projects.
static void greet() {
printf("Hello\n");
}

Dynamic Memory Allocation

Dynamic memory is allocated at runtime using pointers. The main functions are:

malloc()

  • Allocates memory of given size in bytes.
  • Returns a void pointer. For multiple item it return void pointer of first item.
    int *ptr = (int*) malloc(sizeof(int)*5); // store 20 / 4 = 5 integer
    printf("%d\n", ptr);
    printf("%d\n", ptr + 1); // incremented by 4

    char *ch = (char*) malloc(sizeof(int)*5); // store 20 / 1 = 20 character
    printf("%d\n", ch);
    printf("%d\n", ch + 1); // incremented by 1
    • You can cast the allocated memory to any specific type.
  • Memory not initialized (contains garbage).
  • It could be failed, always perform NULL check.

Why is it needed to perform NULL check?

When you use malloc, the allocated memory may contain garbage values left over from previous programs. If you accidentally read an element without assigning a value to it, you'll get garbage data. calloc helps you avoid this problem by initializing the memory to zero.

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

int main() {
int *ptr = (int*)malloc(5 * sizeof(int)); // 5 integers
if(ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
free(ptr); // Always free memory
return 0;
}

Variable Length Array

int n;
scanf("%d", &n); // user inputs size
int arr[n]; // VLA: Variable Length Array
  • n is determined at runtime, not compile-time.
  • This is called a Variable Length Array (VLA), introduced in C99.
  • The array arr is allocated on the stack(which can be overflowed for larger value), not heap.

calloc()

  • Allocates memory for n elements of given size.
  • Memory initialized to 0.
  • It could be failed, always perform NULL check.
int *ptr = (int*)calloc(5, sizeof(int)); // 5 integers initialized to 0

When to use malloc, and when to use calloc?

  • Use calloc when you want all values to start from 0 — for example, counters, score trackers, frequency arrays, etc.
  • Use malloc when you know you will assign all the values immediately after allocation — in that case, there’s no need to initialize with 0, and the extra work of calloc (zero-filling) just wastes time.

realloc()

  • Resizes previously allocated memory block.
  • Can increase or decrease memory size.
  • Returns new pointer (may change address).
ptr = (int*)realloc(ptr, 10 * sizeof(int)); // resize to 10 integers
  • It either add new space after the last allocated memory
  • Or create completly new space if there is not enough memory after the last allocated memory.

Sometimes realloc return NULL, thats why its better to store it in a temp variable

int *temp = (int *)realloc(arr, 10 * sizeof(int));

if (temp != NULL) {
arr = temp;
printf("Resized!");
} else {
printf("Failed, but previous items are safe!", 5);
}

free()

  • Frees dynamically allocated memory to avoid memory leaks(overflow).
  • It doesn't remove the connection, neither clear the value, it just tells the heap manager that the memory block that pointer points to is no longer in use.
free(ptr);
ptr = NULL; // Best practice

Stack memory is cleared when function end, but heap memory is not, it keep in the heap until program execution is finished, free is used to clear heap memory.

Use free only on dynamically allocated memory, not on stack memory.

int x = 42;
int *p = &x;
free(p); // ERROR

To maintain extra safty, assign NULL after clearing a memory.

  1. Always check NULL for malloc and calloc.
  2. Clear the memory to avoid memory leak.
  3. Assing NULL after free the memory.

Summary Table

FunctionPurposeInitializationNotes
malloc()Allocate memoryGarbageReturns void*
calloc()Allocate memory for n elementsZeroSafer than malloc for zero-initialization
realloc()Resize allocated memoryDependsMay move block
free()Deallocate memoryN/APrevents memory leaks

Advanced Pointer

Double Pointer (**)

A double pointer is a pointer that stores the address of another pointer.

type **ptr;
  • Useful for dynamically allocating 2D arrays, passing pointers to functions, or modifying a pointer in a function.
#include <stdio.h>

int main() {
int x = 10;
int *p = &x; // Pointer to x
int **pp = &p; // Pointer to pointer p

printf("x = %d\n", x); // 10
printf("*p = %d\n", *p); // 10
printf("**pp = %d\n", **pp); // 10

**pp = 20; // Modifying x via double pointer
printf("x = %d\n", x); // 20
return 0;
}

Here, **pp accesses the value of x through two levels of indirection.

  1. Why single pointer fails
void makeNull(int *ptr) {
ptr = NULL; // Only changes local copy
}
  • ptr is passed by value
  • So only a copy is modified
  • The original pointer in the caller remains unchanged
void makeNull(int **ptr) {   // pointer to pointer
*ptr = NULL; // modifies original pointer
}
  1. Dynamic allocation problem
void allocateWrong(int *arr, int size) {
arr = (int *)malloc(size * sizeof(int)); // lost after function ends
}
  • Memory is allocated but caller never receives it
  • Causes memory leak
void allocateRight(int **arr, int size) {
*arr = (int *)malloc(size * sizeof(int)); // assign to caller's pointer

if (*arr != NULL) {
for (int i = 0; i < size; i++) {
(*arr)[i] = 0; // initialize values
}
}
}
  1. 2D Dynamic Array (Double Pointer)
int **matrix = (int **)malloc(rows * sizeof(int *));
if (matrix == NULL) return NULL;

for (int i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(cols * sizeof(int));

if (matrix[i] == NULL) {
for (int j = 0; j < i; j++) {
free(matrix[j]);
}
free(matrix);
return NULL;
}
}

Freeing Memory

for (int i = 0; i < rows; i++) {
free(matrix[i]); // free each row first
}
free(matrix); // then free main pointer

Important Notes

  • Always free in reverse order of allocation
  • Freeing matrix first will lose access to rows → memory leak
  • Double pointer (**) is needed when:
  • Modifying a pointer inside a function
  • Returning dynamically allocated memory
  • Creating dynamic 2D arrays

Quick Summary

CaseUse *Use **
Just reading value
Modify pointer
Allocate memory in function
2D array

Pointer with char

const char *str = "Hello";
  • This is a pointer to a string literal.
  • String literals are stored in read-only memory, so modifying them is undefined behavior.
  • That’s why you should use const.
  • str[0] = 'P';not allowed (may crash or cause undefined behavior)

Difference of sizeof

char str1[] = "Hello";
const char *str2 = "Hello";

printf("sizeof(str1) = %lu\n", sizeof(str1)); // 6 → size of array (5 chars + '\0')
printf("sizeof(str2) = %lu\n", sizeof(str2)); // 8 → size of pointer (on 64-bit system)
  • str1 → actual array stored in stack
  • str2pointer pointing to string literal. It is immutable because it points to a string literal stored in read-only memory, not because it’s a pointer.
  • sizeof:
    • On arrays → returns total memory of array
    • On pointers → returns size of pointer itself (4 bytes on 32-bit, 8 bytes on 64-bit)

Pointer size ≠ data type size

Implementation of strcpy

// copy the string from source to destination
void myStrCpy(char *dest, const char *src) {
while (*src != '\0') {
*dest = *src; // copy character from src to dest
dest++;
src++;
}
*dest = '\0'; // add null terminator at the end
}

Short (Advanced) Version

void myStrCpy2(char *dest, const char *src) {
while ((*dest++ = *src++));
}
  • *dest++ = *src++
    • Copies character
    • Then moves both pointers forward
  • The loop stops when '\0' is copied:
    • Assignment returns the assigned value
    • When '\0' (i.e., 0) is assigned → condition becomes false → loop ends
  • The null terminator is also copied automatically

Important Notes

  • Destination buffer must be large enough to hold the copied string.
  • No bounds checking → can cause buffer overflow.
  • Safer alternative: strncpy (but it has its own caveats).

Pointer Array

char days[7][10] = {
"Saturday",
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
};
const char *days[] = {
"Saturday",
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
};

Pointer in Structure

There are two ways to access members:

// Way 1: (*ptr).member — dereference first, then use dot
printf("Name: %s\n", (*ptr).name); // Masum

// Way 2: ptr->member — shorthand version
printf("Name: %s\n", ptr->name); // Masum
printf("Age: %d\n", ptr->age); // 21
printf("GPA: %.2f\n", ptr->gpa); // 3.85

Why (*ptr).name works but *ptr.name does not?

  • *ptr.name
    • *(ptr.name). has higher priority than *
    • trying to access name from ptr (but ptr is a pointer, not a structure!)
    • ERROR
  • (*ptr).name
    • first dereference ptr → get structure
    • then access .name
  • ptr->name
    • does both in one step

Pro Tip: Always use -> with structure pointers. It’s cleaner and more readable than (*ptr)..

You can have pointers as members of a structure, which is useful for dynamic memory allocation or linked data structures.

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

struct Student {
char *name;
int age;
};

int main() {
struct Student s1;

// Dynamically allocate memory for name
s1.name = (char*)malloc(20 * sizeof(char));
if(s1.name == NULL) return 1;

strcpy(s1.name, "Bob");
s1.age = 21;

printf("Name: %s, Age: %d\n", s1.name, s1.age);

free(s1.name); // Free memory
return 0;
}

Here, s1.name is a pointer inside the structure pointing to dynamically allocated memory.

Passing Structure to Functions

Structures can be large. Passing by value copies the whole structure (slow), while passing a pointer only sends the address (fast).

// ❌ Pass by value — full copy
void printStudent(struct Student s) {
printf("%s, %d, %.2f\n", s.name, s.age, s.gpa);
}

// ✅ Pass by pointer — faster
void printStudentPtr(const struct Student *s) {
printf("%s, %d, %.2f\n", s->name, s->age, s->gpa);
}

// ✅ Can also modify original data
void birthday(struct Student *s) {
s->age++; // modifies original
}
int main() {
struct Student s1 = {"Rafiq", 21, 3.85};

printStudentPtr(&s1); // Rafiq, 21, 3.85
birthday(&s1); // age becomes 22
printStudentPtr(&s1); // Rafiq, 22, 3.85

return 0;
}
  • Pass by value: Entire structure is copied (~58 bytes)
  • Pass by pointer: Only address is passed (4 or 8 bytes)

Dynamic Structure — Using malloc

// Stack allocation:
struct Student s1 = {"Rafiq", 21, 3.85}; // destroyed after function ends

// Heap allocation:
struct Student *s2 = (struct Student *)malloc(sizeof(struct Student));

if (s2 != NULL) {
strcpy(s2->name, "Karim"); // cannot assign string directly
s2->age = 22;
s2->gpa = 3.92;

printf("%s, %d, %.2f\n", s2->name, s2->age, s2->gpa);

free(s2);
s2 = NULL;
}

Dynamic Array of Structures

int n = 3;
struct Student *students = (struct Student *)malloc(n * sizeof(struct Student));

if (students != NULL) {
// input
for (int i = 0; i < n; i++) {
printf("Name: ");
scanf("%s", students[i].name);

printf("Age: ");
scanf("%d", &students[i].age);

printf("GPA: ");
scanf("%f", &students[i].gpa);
}

// output
for (int i = 0; i < n; i++) {
printf("%s - Age %d - GPA %.2f\n",
students[i].name,
students[i].age,
students[i].gpa);
}

free(students);
students = NULL;
}

Memory is allocated in a continuous block in the heap.

Self-Referential Structure — Start of Linked List

A structure can contain a pointer to its own type. This is called a self-referential structure — the foundation of linked lists, trees, and graphs.

struct Node {
int data;
struct Node *next;
};

Why not struct Node next;?

Because that would create infinite nesting:

Node → contains Node → contains Node → ...

But a pointer has fixed size (e.g., 8 bytes), so it's safe.

Summary Table

ConceptDescription
Double pointer (**)Pointer to a pointer; allows modifying a pointer indirectly
StringArray of characters ending with \0; can be array or pointer
Structure (struct)Group different types under one name
Pointer in structurePointer inside struct; can point to dynamic memory or another data

Question

whats the differences between arr[i] and i[arr]

Ah, this is a subtle but fun part of C/C++ pointer and array syntax! At first glance, arr[i] and i[arr] look very different, but they actually mean the same thing in C and C++. Here’s why:

Array indexing definition

In C/C++, the array indexing operator [] is defined in terms of pointer arithmetic:

arr[i]  ≡  *(arr + i)
  • arr is the base address of the array.
  • i is the index.
  • *(arr + i) dereferences the pointer at the ith offset from arr.

Commutativity of addition

Pointer addition is commutative, meaning:

arr + i  ≡  i + arr

So:

*(arr + i)  ≡  *(i + arr)  ≡  i[arr]

That’s why i[arr] is valid C/C++ syntax — it just looks weird.

#include <stdio.h>

int main() {
int arr[5] = {10, 20, 30, 40, 50};

printf("%d\n", arr[2]); // prints 30
printf("%d\n", 2[arr]); // also prints 30

return 0;
}

Both arr[2] and 2[arr] print the same value: 30.

When you might see i[arr]

It’s mostly a quirky trick or joke in C. In practice, everyone writes arr[i] for readability. i[arr] rarely appears outside puzzles or interviews.

  • arr[i] and i[arr] are exactly equivalent in C/C++ because array indexing is just pointer arithmetic.
  • i[arr] is legal but confusing to read — stick to arr[i] in real code.
What's the differenece between array and pointer?
  • Array: A collection of elements of the same type stored contiguously in memory. Its size is fixed at compile time (for static arrays).

    int arr[5]; // array of 5 integers
  • Pointer: A variable that stores the memory address of another variable (or array element). Its value can change.

    int *ptr;  // pointer to int

Memory allocation

FeatureArrayPointer
Memory allocationAllocated statically (stack) or dynamically (heap with malloc)Only the pointer variable is allocated; memory it points to can be allocated separately
Exampleint arr[5]; allocates space for 5 integersint *ptr = malloc(5 * sizeof(int)); allocates 5 integers dynamically

Size and sizeof

int arr[5];
int *ptr = arr;

printf("%zu\n", sizeof(arr)); // 5 * sizeof(int)
printf("%zu\n", sizeof(ptr)); // size of pointer (4 or 8 bytes)
  • sizeof(arr) gives the total size of the array.
  • sizeof(ptr) gives the size of the pointer itself, not what it points to.

Behavior in expressions

  • Array name often decays to a pointer in expressions (except sizeof and &).

    int arr[5];
    int *p = arr; // array decays to pointer
  • Pointer arithmetic works with both, but arrays are fixed:

    p + 1; // points to next element
    arr + 1; // also points to next element

Assignment

  • You cannot assign to an array after declaration:

    int arr[5];
    int arr2[5];
    arr = arr2; // ❌ error
  • You can assign pointers:

    int *p1, *p2;
    p1 = p2; // ✅ p1 now points to the same place as p2

Summary

AspectArrayPointer
MemoryFixed size blockVariable that stores an address
Can assign
sizeofTotal array sizeSize of pointer only
DecayDecays to pointer in most expressionsAlready a pointer
Contiguous memoryAlwaysOnly if you allocate it that way

Tip: Think of an array as a named block of memory, and a pointer as a variable that can point to any block. Arrays are like “fixed houses,” pointers are like “movable addresses.”

What is the difference between int *p; and int *p = NULL;?
  • int *p;

    • Declares a pointer p but does NOT initialize it.
    • It contains a garbage (unknown) address.
    • Using it before assigning a valid address → undefined behavior.
  • int *p = NULL;

    • Declares a pointer and initializes it to NULL (no valid address).
    • Safe to check: if (p == NULL)
    • Helps avoid accidental access to random memory.

Summary:

CaseValueSafe?
int *p;Garbage❌ No
int *p = NULL;NULL✅ Yes
Q2: Are arr[3] and *(arr + 3) the same? Why?

Yes, they are equivalent.

Explanation:

  • Array indexing in C is defined using pointer arithmetic.
  • arr[3] is internally interpreted as:
  *(arr + 3)
  • Because:
    • arr = base address
    • arr + 3 = address of the 4th element
    • *(arr + 3) = value at that address

Summary:

  • arr[i] == *(arr + i)
  • This is a fundamental property of arrays in C.
Q3: What is the value of p after calling free(p);?
  • After free(p);, the pointer p still holds the same address, but:
    • That memory is deallocated (freed).
    • The pointer becomes a dangling pointer.

Important:

free(p);
p = NULL; // recommended

Why set to NULL?

  • Prevents accidental use of freed memory.
  • Makes debugging easier.

Summary:

After freePointer ValueStatus
pOld address❌ Dangling
p = NULLNULL✅ Safe
Q4: Between char *s = "Hello"; and char s[] = "Hello";, which one is safe for s[0] = 'J'?
  • Safe: char s[] = "Hello";
  • Not safe: char *s = "Hello";

Explanation:

  1. char *s = "Hello";

    • Points to a string literal.
    • Stored in read-only memory.
    • Modifying it → undefined behavior (crash likely).
  2. char s[] = "Hello";

    • Creates a modifiable array copy.
    • Stored in stack memory.
    • You can safely modify it.
s[0] = 'J'; // safe only in array version

Summary:

DeclarationModifiable?Safe?
char *s❌ No❌ Unsafe
char s[]✅ Yes✅ Safe
Q5: Why should we use a temporary pointer with realloc()?

Because realloc() can fail and return NULL, which may cause memory loss.

Problem:

p = realloc(p, new_size);
  • If realloc fails:
    • Returns NULL
    • Original pointer p is lost → memory leak
int *temp = realloc(p, new_size);
if (temp != NULL) {
p = temp;
}
  • Keeps original pointer safe if reallocation fails.
  • Prevents memory leaks.

Summary:

Without tempRisk
Direct assign❌ Memory leak
With tempBenefit
--------------------------
Safe reassignment✅ No leak
Q6: Why do we need a temporary variable when freeing a linked list?

Because freeing a node destroys access to the next node.

Problem:

free(current);
current = current->next; // ❌ invalid access

Correct Approach:

Node *temp;
while (current != NULL) {
temp = current;
current = current->next;
free(temp);
}

Why?

  • Save next node before freeing current.
  • Prevents accessing freed memory.

Summary:

  • Needed to preserve traversal.
  • Avoids undefined behavior.
Q7: What does sizeof(arr) return if int *arr = malloc(5 * sizeof(int));?
  • sizeof(arr) returns the size of the pointer, NOT the allocated memory.
int *arr = malloc(5 * sizeof(int));
printf("%zu", sizeof(arr));
  • On 64-bit system → 8 bytes
  • On 32-bit system → 4 bytes

Why?

  • arr is a pointer (int *), not an array.
  • sizeof only checks the type at compile time.

Summary:

ExpressionReturns
sizeof(arr)Pointer size
Allocated memory5 × sizeof(int)
What is segmentation fault?

A segmentation fault (segfault) is a runtime error that happens when a program tries to access memory it is not allowed to access. The operating system immediately stops the program to prevent damage or corruption.

Your program’s memory is divided into segments (stack, heap, code, etc.). A segmentation fault occurs when you:

  • Access memory outside your allowed region
  • Access memory in an invalid way (wrong permissions)

Common causes

  1. Dereferencing a NULL pointer
  2. Accessing out-of-bounds array
  3. Using uninitialized pointers
  4. Accessing freed memory
  5. Stack overflow (deep recursion)
  6. Modifying read-only memory

How to resolve / prevent it

  1. Always initialize pointers
  2. Check before dereferencing
  3. Stay within array bounds
  4. Use memory carefully
  5. Avoid modifying string literals