C Programming

Structures and Functions

C Programming Exercise 7 of 7

Structures and Functions

Master this concept with detailed explanations and interactive coding examples.

7 Questions
15 Minutes
0% Completed
#include <stdio.h> int main() { printf("Hello"); return 0; }

Interview Questions & Answers

Technical Interview

Structures 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.

0% read

Interactive Code Playground

Live Coding
main.c
1
$ Ready to run your code...
Tips:
  • Use Ctrl + Enter to run code
  • Include <stdio.h> for printf/scanf
  • main() function must return int
Sample Snippets:

Detailed Explanation

Structures and Functions

Working 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:

Pass by Value - Copy Overhead

Passing large structures by value copies all members, which can be expensive in time and memory.

Pass by Pointer - Efficiency

Passing structure pointers (4/8 bytes) is efficient regardless of structure size. Allows in-place modification.

const Protection

Use 'const struct type *ptr' for read-only access to prevent accidental modifications.

Arrow Operator (->)

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.'

2 min read 5,900 views Trade-off Analysis

Common mistake: Forgetting that modifying a structure passed by value only affects the local copy, not the original. Use pointers when modifications are needed.

2 min read 4,500 views Common Pitfall

Pro tip: For large structures, always pass const pointers to input parameters and non-const pointers for output/modifiable parameters. Self-documenting and efficient.

1 min read 3,700 views Best Practice

Common Follow-up Questions:

What are the different ways to pass a structure to a function? Compare them.
Basic

There are three main ways to pass structures to functions:

  1. Pass by value: void func(struct Student s); - Entire structure is copied. Safe but inefficient for large structures.
  2. Pass by pointer (reference): void func(struct Student *s); - Only pointer (4/8 bytes) is copied. Efficient and allows modifications.
  3. 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;
}
💡 Choose pass-by-value for small structures (≤ 16 bytes) where you need a copy; pass-by-pointer for everything else, especially if modification is needed.
How can a function return a structure? What are the implications?
Intermediate

Functions can return structures in three ways:

  1. 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.
  2. Return pointer to static structure: struct Student* getDefaultStudent() - Returns pointer to static data. Not thread-safe, subsequent calls overwrite.
  3. 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;
}
💡 Return by value is safest but copies data; return static pointer is efficient but not thread-safe; return dynamic pointer gives most control but requires careful memory management.
What is the difference between using dot (.) and arrow (->) operators when working with structures in functions?
Basic

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 variable
  • ptr->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;
}
💡 Use dot with struct variables, arrow with struct pointers. Arrow is just syntactic sugar for (*ptr).member.
How do you use const with structure pointers? Why is it important?
Intermediate

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;
}
💡 Use const for input parameters that shouldn't be modified. It's a contract that makes code safer and self-documenting.
How do you pass an array of structures to a function? Provide examples.
Intermediate

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;
}
💡 Arrays of structures are passed as pointers to the first element. The function receives address of first structure and can use array indexing or pointer arithmetic.
What is structure padding and how does it affect functions that receive structures?
Advanced

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;
}
💡 Structure padding affects memory usage, performance, and portability. Rearrange members (largest first) to minimize padding. Use packed attribute only when necessary (network protocols, file formats).
How do you implement object-oriented concepts like encapsulation using structures and functions?
Advanced

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;
}
💡 C can implement OOP concepts using structures with function pointers. This technique is used in many real-world C libraries (GTK, libuv) for polymorphism and encapsulation.
Question 7 of 7 🎉 Last Question