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
&ameans address of variablea.- The pointer
pnow 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
*pmeans value stored at the address insidep.
| Operator | Meaning |
|---|---|
& | 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.
intpointer → moves 4 bytes (usually)charpointer → 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 valuep++,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
| Feature | Call by Value | Call by Reference |
|---|---|---|
| What is passed | Copy of the variable | Address of the variable |
| Original variable change | Not affected | Affected |
| Memory overhead | Higher for large data | Lower for large data |
| Safety | Safer (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 returnspointer_name→ the name of the pointerparameter_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
- Callback functions – Pass a function as a parameter to another function.
- Dynamic function calls – Choose a function to execute at runtime.
- 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;
}
operationis a function pointer.- It can point to any function that takes two
intarguments and returnsvoid. - 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
ptrgets memory usingmalloc.free(ptr)releases that memory.ptrstill holds the old address, but the memory is no longer owned by the program (ptrhave the address, not the value it was holded).- Accessing
*ptrmay cause garbage values or a crash.
It occur in 3 places
- after
free - local pointer of function scope
- 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
| Feature | static | malloc |
|---|---|---|
| Allocation time | Compile/startup | Runtime |
| Memory location | Data segment | Heap |
| Size | Fixed | Flexible |
| Lifetime | Entire program | Until free() |
| Management | Automatic | Manual |
| Speed | Faster | Slightly slower |
Memory Management
Memory Layout in C
C programs typically have four main memory segments:
-
Code/Text Segment
- Stores compiled program instructions.
- Read-only, fixed size.
-
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;)
- Initialized data segment (e.g.,
-
Stack
- Stores local variables and function call information (return addresses).
- LIFO structure; memory freed automatically when function ends.
-
Heap
- Stores dynamically allocated memory (via
malloc,calloc,realloc). - Must be manually freed using
free().
- Stores dynamically allocated memory (via
+--------------------+
| 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
NULLcheck.
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
nis determined at runtime, not compile-time.- This is called a Variable Length Array (VLA), introduced in C99.
- The array
arris 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
NULLcheck.
int *ptr = (int*)calloc(5, sizeof(int)); // 5 integers initialized to 0
When to use malloc, and when to use calloc?
- Use
callocwhen you want all values to start from 0 — for example, counters, score trackers, frequency arrays, etc. - Use
mallocwhen 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 ofcalloc(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.
- Always check
NULLformallocandcalloc. - Clear the memory to avoid memory leak.
- Assing
NULLafterfreethe memory.
Summary Table
| Function | Purpose | Initialization | Notes |
|---|---|---|---|
| malloc() | Allocate memory | Garbage | Returns void* |
| calloc() | Allocate memory for n elements | Zero | Safer than malloc for zero-initialization |
| realloc() | Resize allocated memory | Depends | May move block |
| free() | Deallocate memory | N/A | Prevents 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.
- Why single pointer fails
void makeNull(int *ptr) {
ptr = NULL; // Only changes local copy
}
ptris 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
}
- 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
}
}
}
- 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
matrixfirst 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
| Case | Use * | 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 stackstr2→ pointer 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(butptris a pointer, not a structure!) - ERROR
(*ptr).name- first dereference
ptr→ get structure - then access
.name
- first dereference
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
| Concept | Description |
|---|---|
Double pointer (**) | Pointer to a pointer; allows modifying a pointer indirectly |
| String | Array of characters ending with \0; can be array or pointer |
Structure (struct) | Group different types under one name |
| Pointer in structure | Pointer 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)
arris the base address of the array.iis the index.*(arr + i)dereferences the pointer at theith offset fromarr.
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]andi[arr]are exactly equivalent in C/C++ because array indexing is just pointer arithmetic.i[arr]is legal but confusing to read — stick toarr[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
| Feature | Array | Pointer |
|---|---|---|
| Memory allocation | Allocated statically (stack) or dynamically (heap with malloc) | Only the pointer variable is allocated; memory it points to can be allocated separately |
| Example | int arr[5]; allocates space for 5 integers | int *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
sizeofand&).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
| Aspect | Array | Pointer |
|---|---|---|
| Memory | Fixed size block | Variable that stores an address |
| Can assign | ❌ | ✅ |
sizeof | Total array size | Size of pointer only |
| Decay | Decays to pointer in most expressions | Already a pointer |
| Contiguous memory | Always | Only 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
pbut does NOT initialize it. - It contains a garbage (unknown) address.
- Using it before assigning a valid address → undefined behavior.
- Declares a pointer
-
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:
| Case | Value | Safe? |
|---|---|---|
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 addressarr + 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 pointerpstill 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 free | Pointer Value | Status |
|---|---|---|
p | Old address | ❌ Dangling |
p = NULL | NULL | ✅ 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:
-
char *s = "Hello";- Points to a string literal.
- Stored in read-only memory.
- Modifying it → undefined behavior (crash likely).
-
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:
| Declaration | Modifiable? | 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
pis lost → memory leak
- Returns
int *temp = realloc(p, new_size);
if (temp != NULL) {
p = temp;
}
- Keeps original pointer safe if reallocation fails.
- Prevents memory leaks.
Summary:
| Without temp | Risk |
|---|---|
| Direct assign | ❌ Memory leak |
| With temp | Benefit |
| ----------------- | --------- |
| 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?
arris a pointer (int *), not an array.sizeofonly checks the type at compile time.
Summary:
| Expression | Returns |
|---|---|
sizeof(arr) | Pointer size |
| Allocated memory | 5 × 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
- Dereferencing a NULL pointer
- Accessing out-of-bounds array
- Using uninitialized pointers
- Accessing freed memory
- Stack overflow (deep recursion)
- Modifying read-only memory
How to resolve / prevent it
- Always initialize pointers
- Check before dereferencing
- Stay within array bounds
- Use memory carefully
- Avoid modifying string literals