Docs
README
Pointers to Structures in C
Table of Contents
- β’Introduction
- β’Declaring Pointer to Structure
- β’Initializing Structure Pointers
- β’The Arrow Operator (->)
- β’Arrow vs Dot Operator
- β’Dynamic Structure Allocation
- β’Passing Structures by Reference
- β’Linked Data Structures
- β’Self-Referential Structures
- β’Function Pointers in Structures
- β’Common Patterns
- β’Best Practices
- β’Summary
Introduction
Pointers to structures are one of the most powerful features in C programming. They enable:
- β’Efficient function parameters: Pass large structures without copying
- β’Dynamic data structures: Create linked lists, trees, graphs
- β’Memory efficiency: Allocate structures at runtime
- β’Object-oriented patterns: Simulate OOP concepts in C
Why Use Structure Pointers?
Without pointers: Copy entire structure (expensive for large structures)
With pointers: Copy only address (8 bytes on 64-bit systems)
struct Person person; // 100 bytes
struct Person *ptr; // 8 bytes (pointer)
Declaring Pointer to Structure
Basic Declaration Syntax
struct StructureName *pointerName;
Examples
// First, define a structure
struct Person {
char name[50];
int age;
float salary;
};
// Declare pointer to structure
struct Person *personPtr;
// With typedef
typedef struct {
int x;
int y;
} Point;
Point *pointPtr;
Visual Representation
struct Person person = {"John", 25, 50000.0};
struct Person *ptr = &person;
Memory Layout:
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β person (struct) β
β ββββββββββββββββ¬ββββββββ¬ββββββββββββββββββ β
β β name[50] β age β salary β β
β β "John" β 25 β 50000.0 β β
β ββββββββββββββββ΄ββββββββ΄ββββββββββββββββββ β
β Address: 0x1000 β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββ
β ptr β
β 0x1000 β
β Address: 0x2000 β
βββββββββββββββββββ
Initializing Structure Pointers
Method 1: Address of Existing Structure
struct Person person = {"Alice", 30, 60000.0};
struct Person *ptr = &person; // Point to existing structure
Method 2: Dynamic Allocation
struct Person *ptr = malloc(sizeof(struct Person));
// Don't forget to check for NULL and free later!
Method 3: Array Element Address
struct Person people[10];
struct Person *ptr = &people[0]; // Or just: ptr = people;
struct Person *ptr2 = &people[5]; // Point to 6th element
Method 4: Function Return
struct Person* createPerson(const char *name, int age) {
struct Person *p = malloc(sizeof(struct Person));
if (p != NULL) {
strcpy(p->name, name);
p->age = age;
}
return p;
}
struct Person *ptr = createPerson("Bob", 25);
The Arrow Operator (->)
Introduction
The arrow operator -> is used to access structure members through a pointer. It combines dereferencing and member access.
Syntax
pointer->member
Equivalence
ptr->member β‘ (*ptr).member
Why Arrow Operator Exists
// Without arrow operator (cumbersome)
(*ptr).name
(*ptr).age
(*ptr).salary
// With arrow operator (clean)
ptr->name
ptr->age
ptr->salary
Practical Example
struct Person {
char name[50];
int age;
float salary;
};
struct Person person = {"Charlie", 35, 75000.0};
struct Person *ptr = &person;
// Using arrow operator
printf("Name: %s\n", ptr->name); // Charlie
printf("Age: %d\n", ptr->age); // 35
printf("Salary: %.2f\n", ptr->salary); // 75000.00
// Modifying through pointer
ptr->age = 36;
ptr->salary = 80000.0;
Arrow vs Dot Operator
Comparison Table
| Operator | Used With | Syntax | Example |
|---|---|---|---|
. (dot) | Structure variable | struct.member | person.name |
-> (arrow) | Pointer to structure | ptr->member | ptr->name |
When to Use Which
struct Person person; // Structure variable
struct Person *ptr = &person; // Pointer to structure
// Use dot with structure variable
person.age = 25;
// Use arrow with pointer
ptr->age = 25;
// Alternative (not recommended)
(*ptr).age = 25; // Same as ptr->age
Visual Comparison
Direct Access (dot): Indirect Access (arrow):
βββββββββββββββββββ βββββββββββββββββββ
β person β β ptr ββββββββ
β βββββββββββββββ β βββββββββββββββββββ β
β β age: 25 β β β
β βββββββββββββββ β βββββββββββββββββββ β
βββββββββββββββββββ β person ββββββββ
person.age = 25 β βββββββββββββββ β
β β age: 25 β β
β βββββββββββββββ β
βββββββββββββββββββ
ptr->age = 25
Dynamic Structure Allocation
Single Structure Allocation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person {
char name[50];
int age;
};
int main() {
// Allocate memory for one structure
struct Person *ptr = malloc(sizeof(struct Person));
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
// Initialize using arrow operator
strcpy(ptr->name, "David");
ptr->age = 40;
// Use the structure
printf("Name: %s, Age: %d\n", ptr->name, ptr->age);
// Free memory when done
free(ptr);
ptr = NULL; // Prevent dangling pointer
return 0;
}
Array of Structures Allocation
int n = 5;
struct Person *people = malloc(n * sizeof(struct Person));
if (people != NULL) {
// Access using array notation or pointer arithmetic
for (int i = 0; i < n; i++) {
sprintf(people[i].name, "Person%d", i + 1);
people[i].age = 20 + i;
// Or using pointer arithmetic:
// (people + i)->age = 20 + i;
}
free(people);
}
Memory Layout
struct Person *people = malloc(3 * sizeof(struct Person));
Memory:
βββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββ
β people[0] β people[1] β people[2] β
β βββββββββ¬ββββββ β βββββββββ¬ββββββ β βββββββββ¬ββββββ β
β βname β age β β βname β age β β βname β age β β
β βββββββββ΄ββββββ β βββββββββ΄ββββββ β βββββββββ΄ββββββ β
βββββββββββββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββ
β β β
people people+1 people+2
Passing Structures by Reference
Pass by Value vs Pass by Reference
// Pass by Value - Copies entire structure
void updateByValue(struct Person p) {
p.age = 100; // Only modifies copy!
}
// Pass by Reference - Passes pointer
void updateByReference(struct Person *p) {
p->age = 100; // Modifies original!
}
Performance Comparison
struct LargeStruct {
int data[1000];
char buffer[5000];
};
// BAD: Copies 24000+ bytes
void processByValue(struct LargeStruct s) { ... }
// GOOD: Copies only 8 bytes (pointer)
void processByReference(struct LargeStruct *s) { ... }
// BEST: Use const for read-only access
void printByReference(const struct LargeStruct *s) { ... }
Complete Example
#include <stdio.h>
#include <string.h>
struct Employee {
char name[50];
int id;
float salary;
};
// Read-only access (use const)
void printEmployee(const struct Employee *emp) {
printf("ID: %d, Name: %s, Salary: %.2f\n",
emp->id, emp->name, emp->salary);
}
// Modify structure
void giveRaise(struct Employee *emp, float percent) {
emp->salary *= (1.0 + percent / 100.0);
}
// Initialize structure
void initEmployee(struct Employee *emp, int id,
const char *name, float salary) {
emp->id = id;
strncpy(emp->name, name, sizeof(emp->name) - 1);
emp->name[sizeof(emp->name) - 1] = '\0';
emp->salary = salary;
}
int main() {
struct Employee emp;
initEmployee(&emp, 101, "John Doe", 50000.0);
printEmployee(&emp);
giveRaise(&emp, 10); // 10% raise
printf("After raise:\n");
printEmployee(&emp);
return 0;
}
Linked Data Structures
Introduction to Linked Lists
A linked list uses structure pointers to connect nodes:
struct Node {
int data;
struct Node *next; // Pointer to same type
};
Visual Representation
Linked List: 10 -> 20 -> 30 -> NULL
ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ
β 10 β next ββΌββββ 20 β next ββΌββββ 30 β NULL β
ββββββββ΄ββββββββ ββββββββ΄ββββββββ ββββββββ΄ββββββββ
β
head
Basic Linked List Implementation
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
// Create a new node
struct Node* createNode(int data) {
struct Node *newNode = malloc(sizeof(struct Node));
if (newNode != NULL) {
newNode->data = data;
newNode->next = NULL;
}
return newNode;
}
// Insert at beginning
struct Node* insertFront(struct Node *head, int data) {
struct Node *newNode = createNode(data);
if (newNode != NULL) {
newNode->next = head;
head = newNode;
}
return head;
}
// Print list
void printList(struct Node *head) {
struct Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// Free list
void freeList(struct Node *head) {
struct Node *current = head;
while (current != NULL) {
struct Node *next = current->next;
free(current);
current = next;
}
}
int main() {
struct Node *head = NULL;
head = insertFront(head, 30);
head = insertFront(head, 20);
head = insertFront(head, 10);
printList(head); // Output: 10 -> 20 -> 30 -> NULL
freeList(head);
return 0;
}
Self-Referential Structures
Definition
A self-referential structure contains a pointer to its own type:
struct Node {
int data;
struct Node *next; // Self-reference
};
Common Self-Referential Structures
// Singly Linked List Node
struct SLLNode {
int data;
struct SLLNode *next;
};
// Doubly Linked List Node
struct DLLNode {
int data;
struct DLLNode *prev;
struct DLLNode *next;
};
// Binary Tree Node
struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
};
// Graph Node (Adjacency List)
struct GraphNode {
int vertex;
struct GraphNode *next;
};
Binary Tree Example
struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
};
struct TreeNode* createTreeNode(int data) {
struct TreeNode *node = malloc(sizeof(struct TreeNode));
if (node != NULL) {
node->data = data;
node->left = NULL;
node->right = NULL;
}
return node;
}
// Build a simple tree
// 5
// / \
// 3 7
struct TreeNode *root = createTreeNode(5);
root->left = createTreeNode(3);
root->right = createTreeNode(7);
Function Pointers in Structures
Simulating Object-Oriented Programming
#include <stdio.h>
#include <string.h>
// Structure with function pointers
struct Calculator {
int value;
void (*add)(struct Calculator *self, int n);
void (*subtract)(struct Calculator *self, int n);
void (*display)(const struct Calculator *self);
};
// Method implementations
void calc_add(struct Calculator *self, int n) {
self->value += n;
}
void calc_subtract(struct Calculator *self, int n) {
self->value -= n;
}
void calc_display(const struct Calculator *self) {
printf("Value: %d\n", self->value);
}
// Constructor-like function
void initCalculator(struct Calculator *calc, int initial) {
calc->value = initial;
calc->add = calc_add;
calc->subtract = calc_subtract;
calc->display = calc_display;
}
int main() {
struct Calculator calc;
initCalculator(&calc, 10);
calc.display(&calc); // Value: 10
calc.add(&calc, 5);
calc.display(&calc); // Value: 15
calc.subtract(&calc, 3);
calc.display(&calc); // Value: 12
return 0;
}
Virtual Table Pattern
// Operation interface
struct Operations {
int (*calculate)(int a, int b);
const char* (*getName)(void);
};
// Addition operations
int add_calc(int a, int b) { return a + b; }
const char* add_name(void) { return "Addition"; }
struct Operations addOps = {add_calc, add_name};
// Multiplication operations
int mul_calc(int a, int b) { return a * b; }
const char* mul_name(void) { return "Multiplication"; }
struct Operations mulOps = {mul_calc, mul_name};
// Use through pointer
void performOperation(struct Operations *ops, int a, int b) {
printf("%s of %d and %d = %d\n",
ops->getName(), a, b, ops->calculate(a, b));
}
int main() {
performOperation(&addOps, 5, 3); // Addition of 5 and 3 = 8
performOperation(&mulOps, 5, 3); // Multiplication of 5 and 3 = 15
return 0;
}
Common Patterns
Pattern 1: Factory Function
struct Person* Person_create(const char *name, int age) {
struct Person *p = malloc(sizeof(struct Person));
if (p != NULL) {
strncpy(p->name, name, sizeof(p->name) - 1);
p->name[sizeof(p->name) - 1] = '\0';
p->age = age;
}
return p;
}
void Person_destroy(struct Person *p) {
free(p);
}
Pattern 2: Opaque Pointer
// In header file (person.h)
typedef struct Person Person;
Person* Person_create(const char *name, int age);
void Person_destroy(Person *p);
const char* Person_getName(const Person *p);
int Person_getAge(const Person *p);
// In source file (person.c)
struct Person {
char name[50];
int age;
};
// Implementation hidden from users
Pattern 3: Handle-Based API
typedef struct {
int id;
void *internal; // Implementation details hidden
} Handle;
Handle* createHandle(void);
void destroyHandle(Handle *h);
int performOperation(Handle *h, int param);
Best Practices
1. Always Check malloc Return Value
// GOOD
struct Person *p = malloc(sizeof(struct Person));
if (p == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
// BAD
struct Person *p = malloc(sizeof(struct Person));
p->age = 25; // Crashes if malloc failed!
2. Use sizeof with Variable, Not Type
// GOOD - Adapts if type changes
struct Person *p = malloc(sizeof(*p));
// OK but less flexible
struct Person *p = malloc(sizeof(struct Person));
3. Nullify Pointers After Free
free(ptr);
ptr = NULL; // Prevent dangling pointer access
4. Use const for Read-Only Access
// Function won't modify the structure
void printPerson(const struct Person *p) {
printf("%s, %d\n", p->name, p->age);
}
5. Document Ownership
// Caller owns returned pointer (must free)
struct Person* createPerson(void);
// Function takes ownership (will free internally)
void destroyPerson(struct Person *p);
// Function borrows pointer (caller keeps ownership)
void printPerson(const struct Person *p);
6. Handle Nested Pointers Carefully
struct Person {
char *name; // Dynamically allocated
int age;
};
void freePerson(struct Person *p) {
if (p != NULL) {
free(p->name); // Free nested first
free(p); // Then free structure
}
}
Common Mistakes to Avoid
Mistake 1: Using Dot Instead of Arrow
struct Person *ptr = &person;
ptr.age = 25; // ERROR! Use -> with pointers
ptr->age = 25; // CORRECT
Mistake 2: Forgetting to Allocate Memory
struct Person *ptr;
ptr->age = 25; // ERROR! ptr is uninitialized/dangling
// CORRECT
struct Person *ptr = malloc(sizeof(struct Person));
if (ptr != NULL) {
ptr->age = 25;
}
Mistake 3: Not Freeing Nested Allocations
struct Person {
char *name;
};
// WRONG - Memory leak
free(person); // name is leaked!
// CORRECT
free(person->name);
free(person);
Mistake 4: Using Freed Pointer
free(ptr);
printf("%d\n", ptr->age); // UNDEFINED BEHAVIOR!
// CORRECT
free(ptr);
ptr = NULL;
Summary
Key Concepts
| Concept | Description |
|---|---|
| Structure Pointer | Pointer variable that stores address of a structure |
| Arrow Operator (->) | Access member through pointer: ptr->member |
| Dynamic Allocation | Create structures at runtime with malloc |
| Pass by Reference | Efficient function parameter passing |
| Self-Referential | Structure containing pointer to same type |
| Linked Structures | Dynamic data structures using pointers |
Essential Equivalences
ptr->member β‘ (*ptr).member
ptr[i] β‘ *(ptr + i)
&ptr->member β‘ &((*ptr).member)
Memory Management Checklist
- β’β Check malloc return value
- β’β Initialize all members
- β’β Free in reverse order of allocation
- β’β Set pointer to NULL after free
- β’β Free nested pointers first
- β’β Document ownership clearly
Navigation
| Previous | Up | Next |
|---|---|---|
| Array of Structures | Structures and Unions | Unions |