C Structs and Memory
Introduction to Structures and Memory
Structures (structs) in C are user-defined data types that allow you to group related variables of different types under a single name. Understanding how structs interact with memory is crucial for effective C programming, especially when dealing with dynamic memory allocation, memory layout, and performance optimization.
Key concepts for structs and memory:
- Memory layout and alignment of struct members
- Dynamic allocation of structs
- Arrays of structs and struct pointers
- Memory padding and struct packing
- Passing structs to functions efficiently
Basic Struct Declaration and Memory Layout
#include <stdio.h>
struct Person {
char name[50];
int age;
float height;
double salary;
};
int main(void) {
struct Person person1;
printf("Size of struct Person: %zu bytes\n", sizeof(struct Person));
printf("Size of name: %zu bytes\n", sizeof(person1.name));
printf("Size of age: %zu bytes\n", sizeof(person1.age));
printf("Size of height: %zu bytes\n", sizeof(person1.height));
printf("Size of salary: %zu bytes\n", sizeof(person1.salary));
printf("\nMemory layout:\n");
printf("Address of person1: %p\n", (void*)&person1);
printf("Address of name: %p\n", (void*)&person1.name);
printf("Address of age: %p\n", (void*)&person1.age);
printf("Address of height: %p\n", (void*)&person1.height);
printf("Address of salary: %p\n", (void*)&person1.salary);
return 0;
}
Size of struct Person: 72 bytes Size of name: 50 bytes Size of age: 4 bytes Size of height: 4 bytes Size of salary: 8 bytes Memory layout: Address of person1: 0x7fff5fbff5a0 Address of name: 0x7fff5fbff5a0 Address of age: 0x7fff5fbff5d4 Address of height: 0x7fff5fbff5d8 Address of salary: 0x7fff5fbff5e0
Memory Padding and Alignment
#include <stdio.h>
#include <stddef.h>
struct Example1 {
char a;
int b;
char c;
double d;
};
struct Example2 {
double d;
int b;
char a;
char c;
};
int main(void) {
printf("Struct with padding:\n");
printf("Size of Example1: %zu bytes\n", sizeof(struct Example1));
printf("Offset of a: %zu\n", offsetof(struct Example1, a));
printf("Offset of b: %zu\n", offsetof(struct Example1, b));
printf("Offset of c: %zu\n", offsetof(struct Example1, c));
printf("Offset of d: %zu\n", offsetof(struct Example1, d));
printf("\nOptimized struct layout:\n");
printf("Size of Example2: %zu bytes\n", sizeof(struct Example2));
printf("Offset of d: %zu\n", offsetof(struct Example2, d));
printf("Offset of b: %zu\n", offsetof(struct Example2, b));
printf("Offset of a: %zu\n", offsetof(struct Example2, a));
printf("Offset of c: %zu\n", offsetof(struct Example2, c));
return 0;
}
Struct with padding: Size of Example1: 24 bytes Offset of a: 0 Offset of b: 4 Offset of c: 8 Offset of d: 16 Optimized struct layout: Size of Example2: 16 bytes Offset of d: 0 Offset of b: 8 Offset of a: 12 Offset of c: 13
Dynamic Memory Allocation for Structs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
char name[30];
int id;
float gpa;
};
int main(void) {
struct Student *student = malloc(sizeof *student);
if (student == NULL) return 1;
strcpy(student->name, "John Doe");
student->id = 12345;
student->gpa = 3.85;
printf("Student Information:\n");
printf("Name: %s\n", student->name);
printf("ID: %d\n", student->id);
printf("GPA: %.2f\n", student->gpa);
int numStudents = 3;
struct Student *students = malloc((size_t)numStudents * sizeof *students);
if (students == NULL) {
free(student);
return 1;
}
strcpy(students[0].name, "Alice Smith"); students[0].id = 11111; students[0].gpa = 3.9f;
strcpy(students[1].name, "Bob Johnson"); students[1].id = 22222; students[1].gpa = 3.7f;
strcpy(students[2].name, "Carol Davis"); students[2].id = 33333; students[2].gpa = 3.8f;
printf("\nStudent Array:\n");
for (int i = 0; i < numStudents; i++) {
printf("%s (ID: %d, GPA: %.2f)\n", students[i].name, students[i].id, students[i].gpa);
}
free(student);
free(students);
return 0;
}
Student Information: Name: John Doe ID: 12345 GPA: 3.85 Student Array: Alice Smith (ID: 11111, GPA: 3.90) Bob Johnson (ID: 22222, GPA: 3.70) Carol Davis (ID: 33333, GPA: 3.80)
Structs with Dynamic Members
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct DynamicString {
char *text;
int length;
int capacity;
};
struct DynamicString* createString(const char *initial) {
struct DynamicString *str = malloc(sizeof *str);
if (!str) return NULL;
size_t len = strlen(initial);
str->length = (int)len;
str->capacity = (int)len + 1 + 10; // extra space plus terminator
str->text = malloc((size_t)str->capacity);
if (!str->text) { free(str); return NULL; }
strcpy(str->text, initial);
return str;
}
void freeString(struct DynamicString *str) {
if (str) {
free(str->text);
free(str);
}
}
int main(void) {
struct DynamicString *myStr = createString("Hello, World!");
if (!myStr) return 1;
printf("Text: %s\nLength: %d\nCapacity: %d\n", myStr->text, myStr->length, myStr->capacity);
freeString(myStr);
return 0;
}
Text: Hello, World! Length: 13 Capacity: 24
Passing Structs to Functions
#include <stdio.h>
struct Point { double x, y; };
void printPointByValue(struct Point p) {
printf("Point (by value): (%.2f, %.2f)\n", p.x, p.y);
}
void printPointByPointer(const struct Point *p) {
printf("Point (by pointer): (%.2f, %.2f)\n", p->x, p->y);
}
void movePoint(struct Point *p, double dx, double dy) {
p->x += dx;
p->y += dy;
}
int main(void) {
struct Point p1 = {3.0, 4.0};
printPointByValue(p1);
printPointByPointer(&p1);
movePoint(&p1, 1.0, -1.0);
printf("After move: (%.2f, %.2f)\n", p1.x, p1.y);
return 0;
}
Point (by value): (3.00, 4.00) Point (by pointer): (3.00, 4.00) After move: (4.00, 3.00)
Memory Management Best Practices
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Employee {
char *name;
int *scores;
int numScores;
};
struct Employee* createEmployee(const char *name, int numScores) {
struct Employee *emp = malloc(sizeof *emp);
if (!emp) return NULL;
emp->name = malloc(strlen(name) + 1);
if (!emp->name) { free(emp); return NULL; }
strcpy(emp->name, name);
if (numScores > 0) {
emp->scores = calloc((size_t)numScores, sizeof *emp->scores);
if (!emp->scores) { free(emp->name); free(emp); return NULL; }
emp->numScores = numScores;
} else {
emp->scores = NULL;
emp->numScores = 0;
}
return emp;
}
void freeEmployee(struct Employee *emp) {
if (emp) {
free(emp->name);
free(emp->scores);
free(emp);
}
}
int main(void) {
struct Employee *emp = createEmployee("John Smith", 3);
if (!emp) return 1;
emp->scores[0] = 85; emp->scores[1] = 92; emp->scores[2] = 78;
printf("Employee: %s\nScores: %d, %d, %d\n", emp->name, emp->scores[0], emp->scores[1], emp->scores[2]);
freeEmployee(emp);
return 0;
}
Employee: John Smith Scores: 85, 92, 78
Common Memory Errors with Structs
- Memory leaks: Forgetting to free dynamically allocated members
- Double free: Freeing the same memory twice
- Use after free: Accessing members after freeing
- Dangling pointers: Keeping references to freed memory
- Shallow copies: Copying struct pointers instead of contents
- Uninitialized pointers: Not setting pointers to NULL
- Buffer overruns: Writing past struct member boundaries
Advanced Struct Techniques
Advanced struct techniques include:
1. Memory pools for bulk allocations
2. Compiler directives to control padding
3. Flexible array members for variable-sized structs
4. Using unions to share memory
5. Const correctness for safe pointers
6. Memory-mapped files for handling large data