Mastering Structures and Pointers in C: A Complete Beginner’s Guide with Examples

目次

1. Introduction

The C programming language is widely used in system development and embedded programming. Among its many features, structures and pointers are essential for efficient data management and memory operations. In this article, we will explain these concepts in detail, from the basics to more advanced applications.

By reading this article, you will gain a solid understanding of the role of structures and pointers in C, and you will be able to master their usage through practical code examples. Even beginners will find it easy to follow, as we include concrete examples step by step.

2. Basics of Structures and Pointers

What is a Structure?

A structure is a data structure that allows you to group together multiple data types into a single unit. For example, it is useful when you want to manage a person’s information (such as name, age, and height) as one entity.

The following code shows a basic definition and usage example of a structure.

#include <stdio.h>

// Definition of a structure
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person person1;  // Declare a structure variable

    // Assign data
    strcpy(person1.name, "Taro");
    person1.age = 20;
    person1.height = 170.5;

    // Display data
    printf("Name: %sn", person1.name);
    printf("Age: %dn", person1.age);
    printf("Height: %.1f cmn", person1.height);

    return 0;
}

In this example, we define a structure called Person, which groups three different data types into one structure. This makes it easier to manage related data collectively.

What is a Pointer?

A pointer is a variable that stores the memory address of another variable. It is used to dynamically manipulate memory within a program. Below is a basic example of a pointer:

#include <stdio.h>

int main() {
    int a = 10;
    int *p;  // Declare a pointer variable

    p = &a;  // Assign the address of variable a to the pointer

    printf("Value of a: %dn", a);
    printf("Value pointed to by p: %dn", *p);

    return 0;
}

In this example, the pointer variable p is used to access the value of variable a. Pointers play a powerful role in memory operations, but improper use may lead to bugs or memory leaks, so caution is required.

Relationship Between Structures and Pointers

By combining structures and pointers, you can perform more flexible data manipulation. We will cover this in more detail later, but by mastering the basic concepts, you will be able to move smoothly into advanced applications.

侍エンジニア塾

3. What is a Structure?

Basic Definition of a Structure

A structure is a data structure used to group together multiple types of data. In C, it is commonly used to organize related information and make data management more concise.

Here is an example of a structure definition:

struct Person {
    char name[50];
    int age;
    float height;
};

In this example, we define a structure called Person with the following three members:

  • name: stores a string (array) for the name
  • age: stores an integer value for age
  • height: stores a floating-point number for height

Defining a structure creates a “type” that you can use to declare actual variables.

Declaring and Using Structure Variables

To use a structure, you first need to declare a variable. Here’s an example:

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person person1;  // Declare a structure variable

    // Assign data
    strcpy(person1.name, "Taro");
    person1.age = 20;
    person1.height = 170.5;

    // Display data
    printf("Name: %sn", person1.name);
    printf("Age: %dn", person1.age);
    printf("Height: %.1f cmn", person1.height);

    return 0;
}

In this code, we declare a structure variable named person1 and assign values to its members.

Initializing Structures

You can also initialize a structure variable at the time of declaration:

struct Person person2 = {"Hanako", 25, 160.0};

This approach allows you to write concise code by initializing all members at once.

Arrays of Structures

If you want to manage multiple data items, you can use an array of structures:

struct Person people[2] = {
    {"Taro", 20, 170.5},
    {"Hanako", 25, 160.0}
};

for (int i = 0; i < 2; i++) {
    printf("Name: %s, Age: %d, Height: %.1f cmn", people[i].name, people[i].age, people[i].height);
}

In this example, two sets of data are stored in an array, and processed together using a loop.

Passing Structures to Functions

Structures can also be passed to functions for processing. Here’s an example:

void printPerson(struct Person p) {
    printf("Name: %s, Age: %d, Height: %.1f cmn", p.name, p.age, p.height);
}

This function takes a structure as an argument and prints its information.

Summary

Structures are very convenient for managing related data as a single unit. By mastering the basics, you can make your data organization and access more efficient.

4. Basics of Pointers

What is a Pointer?

A pointer is a powerful C feature that lets you directly work with memory addresses. In this section, we’ll cover the basic concepts of pointers, how to declare and use them, and walk through practical examples.

Declaring and Initializing Pointers

Pointers are declared by placing an * before the variable name:

int a = 10;     // Regular variable
int *p;         // Declare a pointer variable
p = &a;         // Assign the address of a to p
  • *p represents the value stored at the address the pointer refers to (dereferencing).
  • &a retrieves the address of variable a (address-of operator).

Manipulating Values with Pointers

Here’s an example of modifying values using pointers:

#include <stdio.h>

int main() {
    int a = 10;      // Regular variable
    int *p = &a;     // Declare pointer p and assign a’s address

    printf("Value of a: %dn", a);           // 10
    printf("Address of a: %pn", &a);       // Address of a
    printf("Value stored in p (address): %pn", p); 
    printf("Value pointed to by p: %dn", *p); // 10

    *p = 20;  // Change value through pointer
    printf("New value of a: %dn", a);  // 20

    return 0;
}

In this code, we use pointer p to indirectly modify the value of variable a.

Arrays and Pointers

You can also access array elements using pointers:

#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *p = arr; // Points to the first element of the array

    printf("First element: %dn", *p);     // 10
    printf("Second element: %dn", *(p+1)); // 20
    printf("Third element: %dn", *(p+2)); // 30

    return 0;
}

In this example, we use pointer p to access each element of the array.

Summary

Pointers are a fundamental feature of C that enable efficient memory management and flexible program design. In this section, we covered the basics of pointers and how to use them. In the next section, we’ll dive into “5. Combining Structures and Pointers”.

5. Combining Structures and Pointers

Basics of Structure Pointers

By combining structures and pointers, you can achieve more flexible and efficient data management. In this section, we’ll cover the basics of using structure pointers along with practical examples.

Here’s a basic example of a structure pointer:

#include <stdio.h>
#include <string.h>

// Structure definition
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person person1 = {"Taro", 20, 170.5}; // Initialize structure
    struct Person *p = &person1;                // Declare and initialize a structure pointer

    // Access data through the pointer
    printf("Name: %sn", p->name);
    printf("Age: %dn", p->age);
    printf("Height: %.1f cmn", p->height);

    // Modify values through the pointer
    p->age = 25;
    printf("Updated Age: %dn", p->age);

    return 0;
}

Working with Dynamic Memory Allocation

Structure pointers work well with dynamic memory allocation, making it easier to handle large amounts of data. Here’s an example:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Structure definition
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    // Create a structure using dynamic memory allocation
    struct Person *p = (struct Person *)malloc(sizeof(struct Person));

    // Assign data
    strcpy(p->name, "Hanako");
    p->age = 22;
    p->height = 160.0;

    // Display data
    printf("Name: %sn", p->name);
    printf("Age: %dn", p->age);
    printf("Height: %.1f cmn", p->height);

    // Free allocated memory
    free(p);

    return 0;
}

Arrays and Structure Pointers

You can also combine arrays and structure pointers for efficient management of multiple data items:

#include <stdio.h>
#include <string.h>

// Structure definition
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person people[2] = {{"Taro", 20, 170.5}, {"Hanako", 25, 160.0}};
    struct Person *p = people; // Pointer to the first element of the array

    for (int i = 0; i < 2; i++) {
        printf("Name: %sn", (p + i)->name);
        printf("Age: %dn", (p + i)->age);
        printf("Height: %.1f cmn", (p + i)->height);
    }

    return 0;
}

Summary

By combining structures and pointers, you can improve efficiency in data management and gain flexibility in memory operations. This section covered the basics as well as how to use dynamic memory allocation.

6. Using Structures with Functions

Ways to Pass Structures to Functions

There are two main ways to pass structures to functions:

  1. Pass by Value
    A full copy of the structure is passed to the function. For large data, this may consume more memory.
  2. Pass by Reference (Pointer Passing)
    By passing the address of a structure, you improve memory efficiency and can directly modify the original data within the function.

Example: Pass by Value

#include <stdio.h>
#include <string.h>

// Structure definition
struct Person {
    char name[50];
    int age;
};

// Function: pass by value
void printPerson(struct Person p) {
    printf("Name: %sn", p.name);
    printf("Age: %dn", p.age);
}

int main() {
    struct Person person1 = {"Taro", 20};
    printPerson(person1);  // Pass by value

    return 0;
}

In this example, the printPerson function takes a structure by value. However, passing large structures this way may not be memory-efficient.

Example: Pass by Reference (Pointer Passing)

#include <stdio.h>
#include <string.h>

// Structure definition
struct Person {
    char name[50];
    int age;
};

// Function: pass by pointer
void updateAge(struct Person *p) {
    p->age += 1;  // Increment age
}

void printPerson(const struct Person *p) {
    printf("Name: %sn", p->name);
    printf("Age: %dn", p->age);
}

int main() {
    struct Person person1 = {"Hanako", 25};

    printf("Before update:n");
    printPerson(&person1);

    updateAge(&person1);  // Update age via pointer

    printf("After update:n");
    printPerson(&person1);

    return 0;
}

This example shows how to pass a structure by pointer, allowing the function updateAge to directly modify the original data.

Dynamic Memory and Functions

You can also handle dynamically allocated memory in functions:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Structure definition
struct Person {
    char name[50];
    int age;
};

// Function: initialize memory
struct Person *createPerson(const char *name, int age) {
    struct Person *p = (struct Person *)malloc(sizeof(struct Person));
    strcpy(p->name, name);
    p->age = age;
    return p;
}

// Function: display information
void printPerson(const struct Person *p) {
    printf("Name: %sn", p->name);
    printf("Age: %dn", p->age);
}

// Function: free memory
void deletePerson(struct Person *p) {
    free(p);
}

int main() {
    struct Person *person1 = createPerson("Taro", 30);  // Allocate memory dynamically
    printPerson(person1);

    deletePerson(person1);  // Free memory

    return 0;
}

This example shows how to allocate memory dynamically for a structure, manage it with functions, and free it properly to ensure safe programming.

Summary

In this section, we explored how to use functions with structure pointers. Using pointers allows data sharing between functions and more efficient memory management.

7. Using Pointers Inside Structures

Advantages of Using Pointers in Structures

By including pointers inside a structure, you can achieve more flexible and efficient data management and memory operations. In this section, we’ll cover the basics and practical applications of using pointers within structures.

Basic Example: Dynamic String Management

The following example demonstrates how to use a pointer inside a structure to dynamically manage strings:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Structure definition
struct Person {
    char *name;  // Pointer for name
    int age;
};

// Memory allocation and initialization
void setPerson(struct Person *p, const char *name, int age) {
    p->name = (char *)malloc(strlen(name) + 1);  // Allocate memory dynamically
    strcpy(p->name, name);
    p->age = age;
}

// Display information
void printPerson(const struct Person *p) {
    printf("Name: %sn", p->name);
    printf("Age: %dn", p->age);
}

// Free allocated memory
void freePerson(struct Person *p) {
    free(p->name);  // Free dynamically allocated memory
}

int main() {
    struct Person person;

    // Set data
    setPerson(&person, "Taro", 30);

    // Display data
    printPerson(&person);

    // Free memory
    freePerson(&person);

    return 0;
}

In this example, dynamic memory allocation allows managing string data without being limited by fixed array sizes. After use, memory is freed with free.

Combining Arrays and Pointers

When handling multiple data items, pointers allow for flexible and dynamic management:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Structure definition
struct Student {
    char *name;
    int score;
};

// Memory allocation and initialization
struct Student *createStudent(const char *name, int score) {
    struct Student *s = (struct Student *)malloc(sizeof(struct Student));
    s->name = (char *)malloc(strlen(name) + 1);
    strcpy(s->name, name);
    s->score = score;
    return s;
}

// Free allocated memory
void freeStudent(struct Student *s) {
    free(s->name);
    free(s);
}

int main() {
    // Array of student information
    struct Student *students[2];
    students[0] = createStudent("Taro", 85);
    students[1] = createStudent("Hanako", 90);

    // Display data
    for (int i = 0; i < 2; i++) {
        printf("Name: %s, Score: %dn", students[i]->name, students[i]->score);
    }

    // Free memory
    for (int i = 0; i < 2; i++) {
        freeStudent(students[i]);
    }

    return 0;
}

This program dynamically manages student data, allowing flexible handling of multiple items.

Summary

By using pointers inside structures, you can design dynamic memory management and complex data structures more easily. This section covered both basic and applied examples.

8. Practical Example: Creating a Linked List

Basic Structure of a Linked List

A linked list is a data structure that manages data in nodes, allowing dynamic insertion and deletion of elements. In C, you can implement it using structures and pointers.

Its structure looks like this:

[Data | Pointer to next node] → [Data | Pointer to next node] → NULL

Each node holds data and a pointer to the next node. The last node’s pointer is NULL, marking the end of the list.

Defining a Node

Here’s the definition of a node for a linked list:

#include <stdio.h>
#include <stdlib.h>

// Node definition
struct Node {
    int data;            // Data
    struct Node *next;   // Pointer to next node
};

Appending a Node

The following code appends a new node at the end of the linked list:

void append(struct Node **head, int newData) {
    // Create new node
    struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
    struct Node *last = *head;  // Pointer to traverse to the end

    newNode->data = newData;  // Assign data
    newNode->next = NULL;     // New node points to NULL (end of list)

    // If the list is empty
    if (*head == NULL) {
        *head = newNode;
        return;
    }

    // Traverse to the end of the list
    while (last->next != NULL) {
        last = last->next;
    }

    // Add new node at the end
    last->next = newNode;
}

Displaying Nodes

Here’s a function to print all nodes in the list:

void printList(struct Node *node) {
    while (node != NULL) {
        printf("%d -> ", node->data);
        node = node->next;
    }
    printf("NULLn");
}

Deleting a Node

Here’s a function to delete a node with a specific value:

void deleteNode(struct Node **head, int key) {
    struct Node *temp = *head, *prev;

    // If the head node itself holds the key
    if (temp != NULL && temp->data == key) {
        *head = temp->next;
        free(temp);
        return;
    }

    // Search for the node to be deleted
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    // Key not found
    if (temp == NULL) return;

    // Unlink the node
    prev->next = temp->next;
    free(temp);
}

Full Example: Linked List Operations

Here’s a complete program combining the above functions:

#include <stdio.h>
#include <stdlib.h>

// Node definition
struct Node {
    int data;
    struct Node *next;
};

// Append node
void append(struct Node **head, int newData) {
    struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
    struct Node *last = *head;

    newNode->data = newData;
    newNode->next = NULL;

    if (*head == NULL) {
        *head = newNode;
        return;
    }

    while (last->next != NULL) {
        last = last->next;
    }

    last->next = newNode;
}

// Print list contents
void printList(struct Node *node) {
    while (node != NULL) {
        printf("%d -> ", node->data);
        node = node->next;
    }
    printf("NULLn");
}

// Delete node
void deleteNode(struct Node **head, int key) {
    struct Node *temp = *head, *prev;

    if (temp != NULL && temp->data == key) {
        *head = temp->next;
        free(temp);
        return;
    }

    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    if (temp == NULL) return;

    prev->next = temp->next;
    free(temp);
}

int main() {
    struct Node *head = NULL;

    // Add nodes
    append(&head, 10);
    append(&head, 20);
    append(&head, 30);

    printf("Linked list:n");
    printList(head);

    // Delete node
    deleteNode(&head, 20);
    printf("After deleting 20:n");
    printList(head);

    return 0;
}

Summary

In this section, we explained how to implement a linked list using structures and pointers.

Linked lists are widely used in algorithms and data management systems because they allow easy resizing and efficient insertion and deletion of elements.

9. Common Mistakes and Debugging Methods

Using structures and pointers in C is very powerful, but incorrect usage can cause crashes or unexpected behavior. In this section, we’ll cover common mistakes and how to fix them.

1. Uninitialized Pointers

Problem Example:

struct Node *p;  // Pointer not initialized
p->data = 10;    // Causes error

Cause:

Pointer p is not initialized and points to an undefined memory address, leading to invalid memory access.

Solution:

Always initialize pointers to valid memory:

struct Node *p = (struct Node *)malloc(sizeof(struct Node));  // Allocate memory
p->data = 10;  // Works correctly

2. Memory Leaks

Problem Example:

struct Node *p = (struct Node *)malloc(sizeof(struct Node));
// Memory not freed after use

Cause:

If memory allocated with malloc is not freed, it remains occupied until the program terminates.

Solution:

Always free memory when no longer needed:

free(p);

For linked lists and other dynamic structures, free all nodes properly:

struct Node *current = head;
struct Node *next;

while (current != NULL) {
    next = current->next;  // Store next node
    free(current);         // Free current node
    current = next;        // Move to next node
}

3. Dangling Pointers

Problem Example:

struct Node *p = (struct Node *)malloc(sizeof(struct Node));
free(p);  // Free memory
p->data = 10;  // Access after free → undefined behavior

Cause:

After freeing memory, the pointer still refers to that location. Using it becomes unsafe.

Solution:

Set pointers to NULL after freeing:

free(p);
p = NULL;

4. Dereferencing NULL Pointers

Problem Example:

struct Node *p = NULL;
p->data = 10;  // Accessing NULL pointer → error

Cause:

Dereferencing a NULL pointer results in a segmentation fault.

Solution:

Check if a pointer is NULL before using it:

if (p != NULL) {
    p->data = 10;
} else {
    printf("Pointer is NULLn");
}

Debugging Methods

1. Using a Debugger

Tools like GDB allow you to check variable values and program flow at runtime:

gcc -g program.c -o program  // Compile with debug info
gdb ./program

2. Debugging with printf

Print addresses and values to verify behavior:

printf("Address: %p, Value: %dn", (void *)p, *p);

3. Detecting Memory Leaks

Use valgrind to detect leaks and invalid memory access:

valgrind --leak-check=full ./program

Summary

In this section, we explained common mistakes when using structures and pointers in C, and how to debug them.

  • Uninitialized pointers
  • Memory leaks
  • Dangling pointers
  • Dereferencing NULL pointers

These issues can cause serious program errors. Always implement safeguards and test thoroughly.

10. Conclusion

Key Points Recap

In the previous sections, we covered structures and pointers in C from basics to practical applications. Let’s recap the main points:

  1. Structures Basics
  • Useful for grouping multiple data types into one unit.
  • Helps organize related data more efficiently.
  1. Pointers Basics
  • Powerful feature to directly manipulate memory addresses.
  • Essential for dynamic memory allocation and data referencing.
  1. Combining Structures and Pointers
  • Improves efficiency in data management.
  • Enables flexible handling with dynamic memory allocation.
  1. Functions and Structure Pointers
  • Allows direct modification of data across functions.
  • Supports memory-efficient program design.
  1. Using Pointers Inside Structures
  • Enables dynamic memory management and handling of complex data structures.
  • Efficiently manages linked lists, matrices, and more.
  1. Implementing Linked Lists
  • Learned how to build dynamic data structures using structures and pointers.
  • Supports easy addition and deletion of elements.
  1. Common Mistakes & Debugging
  • Learned how to prevent and fix pointer-related errors like memory leaks.
  • Introduced debugging tools for safer programming.

Practical Applications

With these concepts, you can now challenge yourself with:

  1. File Management Systems
  • Use structures and pointers to manage file metadata.
  1. Dynamic Data Structures
  • Extend linked lists into stacks and queues.
  1. Game Development & Simulations
  • Manage characters and states using structures efficiently.
  1. Database Management Systems
  • Use structures and pointers for record creation, deletion, and search operations.

Next Steps

  1. Customize Sample Code
  • Modify provided examples to suit your own projects.
  1. Learn Advanced Data Structures
  • Study doubly linked lists, trees, and graphs.
  1. Combine with Algorithms
  • Implement sorting and searching algorithms using structures and pointers.
  1. Improve Debugging & Optimization
  • Use memory analysis tools to optimize performance and stability.

Final Thoughts

Structures and pointers in C are essential concepts that enable efficient and flexible program design. In this article, we’ve covered them in detail, from basics to advanced use cases, with practical code examples.

By applying these techniques in real projects, you’ll be better prepared for advanced system development and algorithm design. Keep practicing and refining your skills to take your C programming to the next level!

侍エンジニア塾