Structures and Functions
Master this concept with detailed explanations and interactive coding examples.
Interview Questions & Answers
Technical InterviewStructures and Functions in C
When programs grow bigger, simple variables and arrays are not enough. You often need to group related data together. That is where structures come in.
Structures allow you to create your own data type by combining different data types into one unit. When structures are used with functions, your program becomes organized and closer to real-world modeling.
1. What is a Structure in C?
A structure is a user-defined data type that groups variables of different types under one name.
Basic Syntax
#include <stdio.h>
struct Student {
int roll;
char name[50];
float marks;
};
int main() {
struct Student s1 = {1, "Rahul", 85.5};
printf("Roll: %d\n", s1.roll);
printf("Name: %s\n", s1.name);
printf("Marks: %.2f\n", s1.marks);
return 0;
}
Explanation: We created a custom data type called Student that stores roll number, name, and marks together.
2. Passing Structure to a Function (Call by Value)
When you pass a structure normally, a copy is sent to the function.
Example
#include <stdio.h>
struct Student {
int roll;
float marks;
};
void display(struct Student s) {
printf("Roll: %d\n", s.roll);
printf("Marks: %.2f\n", s.marks);
}
int main() {
struct Student s1 = {101, 92.5};
display(s1);
return 0;
}
Note: Any changes inside the function will not affect original structure.
3. Passing Structure by Reference (Using Pointer)
This method is more efficient and allows modification of original data.
Example
#include <stdio.h>
struct Student {
int roll;
float marks;
};
void updateMarks(struct Student *s) {
s->marks = 95.0;
}
int main() {
struct Student s1 = {101, 80.0};
updateMarks(&s1);
printf("Updated Marks: %.2f\n", s1.marks);
return 0;
}
Important: Use -> operator when accessing structure members through pointer.
4. Returning Structure from a Function
You can return a structure directly.
Example
#include <stdio.h>
struct Rectangle {
int length;
int width;
};
struct Rectangle createRectangle(int l, int w) {
struct Rectangle r;
r.length = l;
r.width = w;
return r;
}
int main() {
struct Rectangle rect = createRectangle(10, 5);
printf("Area = %d\n", rect.length * rect.width);
return 0;
}
5. Array of Structures
This is useful when handling multiple records.
Example
#include <stdio.h>
struct Student {
int roll;
float marks;
};
int main() {
struct Student students[2] = {
{1, 85.0},
{2, 90.0}
};
for(int i = 0; i < 2; i++) {
printf("Roll: %d, Marks: %.2f\n", students[i].roll, students[i].marks);
}
return 0;
}
6. Structure Inside Structure (Nested Structure)
Example
#include <stdio.h>
struct Date {
int day, month, year;
};
struct Employee {
int id;
struct Date joiningDate;
};
int main() {
struct Employee e1 = {101, {15, 8, 2023}};
printf("Employee ID: %d\n", e1.id);
printf("Joining Date: %d/%d/%d\n",
e1.joiningDate.day,
e1.joiningDate.month,
e1.joiningDate.year);
return 0;
}
7. Practical Example: Student Record System
This combines structures and functions in a real-world scenario.
#include <stdio.h>
struct Student {
int roll;
float marks;
};
void display(struct Student s) {
printf("Roll: %d, Marks: %.2f\n", s.roll, s.marks);
}
float calculateAverage(struct Student students[], int size) {
float sum = 0;
for(int i = 0; i < size; i++) {
sum += students[i].marks;
}
return sum / size;
}
int main() {
struct Student students[3] = {
{1, 80},
{2, 90},
{3, 85}
};
for(int i = 0; i < 3; i++) {
display(students[i]);
}
printf("Average Marks: %.2f\n",
calculateAverage(students, 3));
return 0;
}
Important Interview Questions
- Difference between structure and union?
- Why use pointer to structure instead of direct passing?
- Explain nested structure with example.
- How memory is allocated for structure?
- What is structure padding?
Memory Insight (Advanced Understanding)
- Structure members are stored in contiguous memory.
- Compiler may add padding for alignment.
- Pointer to structure improves performance for large data.
- Structures form the base for linked lists and trees.
Interactive Code Playground
Live CodingTips:
- Use Ctrl + Enter to run code
- Include <stdio.h> for printf/scanf
- main() function must return int
Sample Snippets:
Detailed Explanation
Structures and FunctionsWorking with Structures and Functions in C
Structures (structs) in C allow grouping of different data types into a single unit. When combined with functions, structures become powerful tools for organizing complex data and creating modular code. Understanding how to pass structures to functions, return them, and use pointers to structures is essential for effective C programming.
- Pass by Value - Entire structure can be passed to functions (copy is made). Safe but memory-intensive for large structures.
- Pass by Reference (Pointer) - Passing structure pointer avoids copying, allows modification, and is more efficient.
- Returning Structures - Functions can return structures by value (C99+), returning copies of all members.
- Returning Structure Pointers - Functions can return pointers to structures (static or dynamically allocated).
- Structure as Function Parameter - Entire structure or its pointer can be passed, each with trade-offs in performance and safety.
- const Correctness - Using const with structure pointers prevents unintended modifications.
Key Points to Remember:
Passing large structures by value copies all members, which can be expensive in time and memory.
Passing structure pointers (4/8 bytes) is efficient regardless of structure size. Allows in-place modification.
Use 'const struct type *ptr' for read-only access to prevent accidental modifications.
Shorthand for (*ptr).member. Used to access structure members via pointers.
Expert Interview Tips:
When explaining structure passing, always discuss the trade-off: 'Pass by value is safer but slower; pass by pointer is faster but risks modification.'
Common mistake: Forgetting that modifying a structure passed by value only affects the local copy, not the original. Use pointers when modifications are needed.
Pro tip: For large structures, always pass const pointers to input parameters and non-const pointers for output/modifiable parameters. Self-documenting and efficient.
Common Follow-up Questions:
There are three main ways to pass structures to functions:
- Pass by value:
void func(struct Student s);- Entire structure is copied. Safe but inefficient for large structures. - Pass by pointer (reference):
void func(struct Student *s);- Only pointer (4/8 bytes) is copied. Efficient and allows modifications. - Pass by const pointer:
void func(const struct Student *s);- Efficient read-only access, prevents modifications.
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
float gpa;
};
// Pass by value - gets a copy
void printStudentByValue(struct Student s) {
printf("Name: %s, Age: %d, GPA: %.2f\n", s.name, s.age, s.gpa);
s.age = 100; // Only modifies local copy
}
// Pass by pointer - can modify original
void updateStudent(struct Student *s) {
s->age = 21; // Modifies original using arrow operator
strcpy(s->name, "Updated Name");
}
// Pass by const pointer - read-only
void displayStudent(const struct Student *s) {
printf("Name: %s, Age: %d, GPA: %.2f\n", s->name, s->age, s->gpa);
// s->age = 30; // ERROR! Cannot modify const
}
int main() {
struct Student john = {"John Doe", 20, 3.5};
printStudentByValue(john);
printf("After value pass - Age: %d (unchanged)\n", john.age);
updateStudent(&john);
printf("After pointer pass - Name: %s\n", john.name);
displayStudent(&john);
return 0;
}
Functions can return structures in three ways:
- Return by value:
struct Student createStudent()- Returns a copy of the structure. Works in C99+. Copies all members, which can be inefficient for large structures. - Return pointer to static structure:
struct Student* getDefaultStudent()- Returns pointer to static data. Not thread-safe, subsequent calls overwrite. - Return pointer to dynamically allocated structure:
struct Student* createNewStudent()- Returns pointer to heap memory. Caller must free it.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
char name[50];
int age;
float gpa;
};
// Method 1: Return by value (C99+)
struct Student createStudent(const char *name, int age, float gpa) {
struct Student s;
strcpy(s.name, name);
s.age = age;
s.gpa = gpa;
return s; // Returns copy
}
// Method 2: Return pointer to static structure
struct Student* getDefaultStudent() {
static struct Student default_student = {"Default", 18, 3.0};
return &default_student; // Safe, but subsequent calls overwrite
}
// Method 3: Return pointer to dynamically allocated structure
struct Student* createNewStudent(const char *name, int age, float gpa) {
struct Student *s = (struct Student*)malloc(sizeof(struct Student));
if(s != NULL) {
strcpy(s->name, name);
s->age = age;
s->gpa = gpa;
}
return s; // Caller must free()
}
int main() {
// Method 1 usage
structStudent s1 = createStudent("Alice", 22, 3.8);
printf("By value: %s, %d, %.2f\n", s1.name, s1.age, s1.gpa);
// Method 2 usage
struct Student *s2 = getDefaultStudent();
printf("Static: %s, %d, %.2f\n", s2->name, s2->age, s2->gpa);
// Method 3 usage
struct Student *s3 = createNewStudent("Bob", 25, 3.2);
if(s3 != NULL) {
printf("Dynamic: %s, %d, %.2f\n", s3->name, s3->age, s3->gpa);
free(s3); // MUST free
}
return 0;
}
The dot operator . is used with structure variables to access members. The arrow operator -> is used with pointers to structures and is a shorthand for dereferencing followed by dot.
Examples:
student.age- When student is a structure variableptr->age- When ptr is a pointer to structure (equivalent to(*ptr).age)
#include <stdio.h>
struct Point {
int x;
int y;
};
void modifyByValue(struct Point p) {
p.x = 100; // Dot operator - modifies local copy
p.y = 200;
}
void modifyByPointer(struct Point *p) {
p->x = 300; // Arrow operator - modifies original
p->y = 400; // Equivalent to (*p).y = 400
}
int main() {
struct Point pt1 = {10, 20};
struct Point *ptr = &pt1;
// Using dot with variable
printf("pt1.x = %d, pt1.y = %d\n", pt1.x, pt1.y);
// Using arrow with pointer
printf("ptr->x = %d, ptr->y = %d\n", ptr->x, ptr->y);
// Arrow is equivalent to dereference + dot
printf("(*ptr).x = %d, (*ptr).y = %d\n", (*ptr).x, (*ptr).y);
modifyByValue(pt1);
printf("After modifyByValue: pt1.x = %d (unchanged)\n", pt1.x);
modifyByPointer(&pt1);
printf("After modifyByPointer: pt1.x = %d (changed)\n", pt1.x);
return 0;
}
const with structure pointers indicates that the function will not modify the structure through that pointer. This is crucial for:
- Safety: Prevents accidental modifications
- Documentation: Makes function intent clear
- Optimization: Compiler can make better assumptions
- Interface design: Distinguishes input vs output parameters
#include <stdio.h>
struct Employee {
int id;
char name[50];
double salary;
};
// Read-only function - promises not to modify
void displayEmployee(const struct Employee *emp) {
printf("ID: %d, Name: %s, Salary: %.2f\n",
emp->id, emp->name, emp->salary);
// emp->salary = 100000; // ERROR! Can't modify const
}
// Modification function - needs non-const pointer
void giveRaise(struct Employee *emp, double percent) {
emp->salary *= (1 + percent / 100);
}
// Mixed const and non-const parameters
void processEmployee(const struct Employee *input,
struct Employee *output) {
// input is read-only, output can be modified
output->id = input->id;
output->salary = input->salary * 1.10;
// input->salary = 0; // ERROR! Can't modify input
}
int main() {
struct Employee alice = {101, "Alice", 50000};
displayEmployee(&alice);
giveRaise(&alice, 10);
displayEmployee(&alice);
struct Employee bob;
processEmployee(&alice, &bob);
displayEmployee(&bob);
return 0;
}
Arrays of structures can be passed to functions just like regular arrays. Since the array name decays to a pointer, the function receives a pointer to the first structure. You must also pass the array size separately.
Common patterns:
- Pass array and size for processing
- Pass array for searching/sorting
- Pass pointer to first element and use pointer arithmetic
#include <stdio.h>
#include <string.h>
struct Book {
char title[100];
char author[50];
int year;
float price;
};
// Process array of structures
void displayBooks(const struct Book books[], int count) {
for(int i = 0; i < count; i++) {
printf("%d. %s by %s (%d) - $%.2f\n",
i+1, books[i].title, books[i].author,
books[i].year, books[i].price);
}
}
// Modify array elements
void applyDiscount(struct Book books[], int count, float percent) {
for(int i = 0; i < count; i++) {
books[i].price *= (1 - percent / 100);
}
}
// Search in array
int findBookByTitle(const struct Book books[], int count, const char *title) {
for(int i = 0; i < count; i++) {
if(strcmp(books[i].title, title) == 0) {
return i;
}
}
return -1;
}
// Sort array (using bubble sort for example)
void sortBooksByYear(struct Book books[], int count) {
for(int i = 0; i < count-1; i++) {
for(int j = 0; j < count-i-1; j++) {
if(books[j].year > books[j+1].year) {
struct Book temp = books[j];
books[j] = books[j+1];
books[j+1] = temp;
}
}
}
}
int main() {
struct Book library[4] = {
{"The C Programming Language", "K&R", 1978, 45.00},
{"Clean Code", "Robert Martin", 2008, 40.00},
{"The Pragmatic Programmer", "Hunt & Thomas", 1999, 50.00},
{"Introduction to Algorithms", "CLRS", 2009, 80.00}
};
int count = 4;
printf("Original library:\n");
displayBooks(library, count);
applyDiscount(library, count, 10);
printf("\nAfter 10%% discount:\n");
displayBooks(library, count);
int index = findBookByTitle(library, count, "Clean Code");
if(index != -1) {
printf("\nFound 'Clean Code' at index %d\n", index);
}
sortBooksByYear(library, count);
printf("\nSorted by year:\n");
displayBooks(library, count);
return 0;
}
Structure padding is the insertion of unused bytes by the compiler between structure members to align them in memory for efficient access. This affects:
- Size: sizeof(struct) may be larger than sum of member sizes
- Pass by value: Copies include padding bytes (wasted time/memory)
- Binary I/O: Writing structures directly to files includes padding
- Network transmission: Padding can break protocols expecting packed data
#include <stdio.h>
#include <stddef.h> // for offsetof
// Example 1: With padding
struct Example1 {
char c; // 1 byte
// 3 bytes padding
int i; // 4 bytes
short s; // 2 bytes
// 2 bytes padding (to align structure size to multiple of 4)
}; // Total: 12 bytes
// Example 2: Rearranged to minimize padding
struct Example2 {
int i; // 4 bytes
short s; // 2 bytes
char c; // 1 byte
// 1 byte padding
}; // Total: 8 bytes
// Example 3: Packed structure (GCC/Clang)
struct __attribute__((packed)) Example3 {
char c;
int i;
short s;
}; // Total: 7 bytes (no padding)
void printStructureInfo(const char *name, size_t size,
const char *members) {
printf("%s: size = %zu bytes\n", name, size);
printf(" Members: %s\n", members);
}
void demonstratePaddingEffects() {
printf("=== Structure Padding Demonstration ===\n\n");
printStructureInfo("Example1 (char, int, short)",
sizeof(struct Example1),
"char (1), [3 padding], int (4), short (2), [2 padding]");
printStructureInfo("Example2 (int, short, char)",
sizeof(struct Example2),
"int (4), short (2), char (1), [1 padding]");
printStructureInfo("Example3 (packed)",
sizeof(struct Example3),
"char (1), int (4), short (2) - no padding");
printf("\nOffset of members in Example1:\n");
printf(" offsetof(struct Example1, c) = %zu\n", offsetof(struct Example1, c));
printf(" offsetof(struct Example1, i) = %zu\n", offsetof(struct Example1, i));
printf(" offsetof(struct Example1, s) = %zu\n", offsetof(struct Example1, s));
}
// Function receiving structure by value - copies padding too
void processByValue(struct Example1 ex) {
printf("\nInside processByValue - received structure of %zu bytes\n",
sizeof(ex));
}
int main() {
demonstratePaddingEffects();
struct Example1 ex1 = {'A', 100, 200};
processByValue(ex1); // Copies all 12 bytes including padding
return 0;
}
C can simulate object-oriented programming using structures (as objects) and function pointers (as methods). This enables:
- Encapsulation: Hide structure details in source file, expose only functions
- Methods: Function pointers inside structures
- Polymorphism: Different structures with same function pointer layout
- Constructors/Destructors: Create and destroy functions
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Forward declaration
typedef struct Shape Shape;
// Method type definitions
typedef double (*AreaFunction)(const Shape*);
typedef void (*DisplayFunction)(const Shape*);
// Base Shape structure (like an abstract class)
struct Shape {
char name[20];
AreaFunction area; // Method pointer
DisplayFunction display; // Method pointer
};
// Derived: Rectangle
struct Rectangle {
Shape base; // Inheritance (embed base struct)
double width;
double height;
};
double rectangleArea(const Shape *shape) {
struct Rectangle *rect = (struct Rectangle*)shape;
return rect->width * rect->height;
}
void rectangleDisplay(const Shape *shape) {
struct Rectangle *rect = (struct Rectangle*)shape;
printf("Rectangle [width=%.1f, height=%.1f]",
rect->width, rect->height);
}
// Constructor for Rectangle
struct Rectangle* createRectangle(const char *name,
double width, double height) {
struct Rectangle *rect = malloc(sizeof(struct Rectangle));
if(rect) {
strcpy(rect->base.name, name);
rect->base.area = rectangleArea;
rect->base.display = rectangleDisplay;
rect->width = width;
rect->height = height;
}
return rect;
}
// Derived: Circle
struct Circle {
Shape base;
double radius;
};
double circleArea(const Shape *shape) {
struct Circle *circle = (struct Circle*)shape;
return 3.14159 * circle->radius * circle->radius;
}
void circleDisplay(const Shape *shape) {
struct Circle *circle = (struct Circle*)shape;
printf("Circle [radius=%.1f]", circle->radius);
}
// Constructor for Circle
struct Circle* createCircle(const char *name, double radius) {
struct Circle *circle = malloc(sizeof(struct Circle));
if(circle) {
strcpy(circle->base.name, name);
circle->base.area = circleArea;
circle->base.display = circleDisplay;
circle->radius = radius;
}
return circle;
}
// Polymorphic function - works with any Shape
void printShapeInfo(const Shape *shape) {
printf("\n%s: ", shape->name);
shape->display(shape);
printf(", Area = %.2f\n", shape->area(shape));
}
// Destructor
void destroyShape(Shape *shape) {
free(shape);
}
int main() {
// Create objects
struct Rectangle *rect = createRectangle("Rect1", 5.0, 3.0);
struct Circle *circle = createCircle("Circle1", 2.5);
// Polymorphic usage
printShapeInfo((Shape*)rect);
printShapeInfo((Shape*)circle);
// Array of shapes (polymorphic collection)
Shape *shapes[2] = {(Shape*)rect, (Shape*)circle};
printf("\n=== Processing shape array ===\n");
for(int i = 0; i < 2; i++) {
printShapeInfo(shapes[i]);
}
// Cleanup
destroyShape((Shape*)rect);
destroyShape((Shape*)circle);
return 0;
}