Mastering Structs in C: An In-Depth Guide

Hari Perev
6 min readOct 30, 2023

In the world of programming, data is king. Managing data efficiently is essential, and C, one of the most powerful and versatile programming languages, provides a remarkable tool for this purpose: structures.

In C, structures, often referred to as structs, are a fundamental concept. They allow you to define custom data types by grouping related variables together under a single name. In this blog post, we will explore structs in C, covering their understanding, importance, and providing a practical example.

Understanding Structs

A struct in C is a composite data type that lets you bundle together variables of different data types. This grouping allows you to organize and manipulate related data more easily. Think of a struct as a container for multiple variables, each with its own data type, enclosed within a single structure. The syntax for defining a struct is as follows:

Structs in C
struct YourStructName {
// Members or fields go here
char member1;
int member2;
double member3;
// ...more members
};

In the example above, we’ve defined a struct named YourStructName with three members: including a character (char), an integer (int), and a double (double) .

Structure Definitions

Struct definitions in C

Anonymous Structure:

struct {
int a;
char b;
};

This is an anonymous structure because it is not given a name.
In C and C++, you can define structures without naming them.
In this case, you are defining a structure with two members: an integer ‘a’ and a character ‘b’.

Calling an anonymous structure is not straightforward because you cannot create variables of this type directly. It’s not recommended to use anonymous structures in practice.

Named Structure:

struct name_top {
int a;
char b;
};

This is a named structure called “name_top.” You are defining a structure with the name “name_top” that has two members: an integer ‘a’ and a character ‘b’.

To use this structure, you can declare variables of this type like this:

struct name_top myStruct;

Here, “myStruct” is a variable of type “struct name_top,” and you can access its members as `myStruct.a` and `myStruct.b`.

Named Structure with Variable Declaration:

struct {
int a;
char b;
} name_bot;

In this example, you are defining a structure similar to the anonymous structure, but you are also declaring a variable of this structure type named “name_bot.”

To use this structure and its associated variable, you can declare variables of this type like this:

name_bot myStruct;

Here, “myStruct” is a variable of the anonymous structure type with the name “name_bot,” and you can access its members as `myStruct.a` and `myStruct.b`.

Anonymous vs Named Structure

It’s important to note that anonymous structures are not commonly used because they cannot be easily reused, and their members are not directly accessible in other parts of the code. Named structures, on the other hand, provide a clear and reusable way to define complex data structures in C and C++.

Structure Definition and Variable Declaration in a single statement:

struct name_top {
int a;
char b;
} name_bot;
  1. Structure Definition:
    You are defining a structure named “name_top” with two members: an integer ‘a’ and a character ‘b’. This structure can be used to create variables of type “struct name_top.”
  2. Variable Declaration:
    You are also declaring a variable named “name_bot” of type “struct name_top.” This means you’ve created a variable of the “name_top” structure type and named it “name_bot.”

To use this structure and variable, you can access the members like this:

name_bot myStruct; // Declare a variable of type "name_top" named "myStruct"
myStruct.a = 42; // Assign a value to the 'a' member
myStruct.b = 'X'; // Assign a value to the 'b' member

In this example, “myStruct” is an instance of the “name_top” structure, and you can access and manipulate its members using the dot notation, as shown above.

Typedef structures in C

In C, you can use the typedef keyword to create type aliases for structures. This is helpful for making your code more readable and concise, as well as for defining custom data types.

typedef struct my_name {
int a;
char b;
} my_alias_t;

You are defining a structure named my_name with two members, an integer a and a character b. Additionally, you are using the typedef keyword to create a new type alias named my_alias_t for this structure.

This typedef statement allows you to use my_alias_t as a shorthand for declaring variables of the my_name structure type. For example, you can now declare a variable like this:

my_alias_t myVar;

The use of typedef is particularly useful for enhancing code readability and making your code more maintainable. It provides a more descriptive name (my_alias_t) for the type, which can make your code easier to understand, especially when working with complex data structures.

Packed vs Unpacked Structures

In C and C++, structures are typically laid out in memory with memory alignment and padding to ensure efficient memory access and alignment of data. However, you can control how structures are laid out using the packed attribute or pragma to create packed structures.

Unpacked Structures:

Unpacked structures are the default in C and C++. They follow the standard memory alignment rules for the target architecture. The compiler may insert padding bytes between members to ensure that each member is correctly aligned in memory for efficient access. For example, on many systems, integers are aligned on 4-byte boundaries.

#include <stdio.h>

struct UnpackedStruct {
char a; // 1 byte
int b; // 4 bytes (assuming a typical 4-byte int)
char c; // 1 byte
};

int main() {
printf("Size of UnpackedStruct: %lu\n", sizeof(struct UnpackedStruct));
return 0;
}

// Size of UnpackedStruct: 12
Unpacked Struct in C

The padding in this case is introduced to align the int member b on a 4-byte boundary because accessing an int at an unaligned memory location can lead to performance issues on some architectures.
The compiler’s decision to add padding is based on the specific platform’s memory alignment requirements and the data types used. This is why the size of the structure is 12 bytes on this particular system. The padding ensures that each member is properly aligned in memory for efficient access, at the expense of a larger memory footprint.

Packed Structures:

The provided code below defines a structure named PackedStruct with the __attribute__((packed)) directive in a GCC compiler environment. This directive tells the compiler to create a packed structure, which eliminates padding between structure members to minimize memory usage.

#include <stdio.h>

struct PackedStruct {
char a;
int b;
char c;
}__attribute__((packed));

int main() {
printf("Size of char: %lu\n", sizeof(char));
printf("Size of int: %lu\n", sizeof(int));
printf("Size of PackedStruct: %lu\n", sizeof(struct PackedStruct));
return 0;
}

// Size of char: 1
// Size of int: 4
// Size of PackedStruct: 6
Packed Struct in C

This demonstrates that using the __attribute__((packed)) directive in GCC creates a more memory-efficient structure with no padding, which can be useful when minimizing memory usage is crucial. However, be cautious when using packed structures, as they may lead to unaligned memory access and potential performance issues on certain platforms.

Conclusion

Structs make your code more organized, enhance data abstraction, and offer a clean way to represent related data in a structured manner.

In conclusion, structs are an essential feature in C, and understanding their usage is fundamental to mastering the language. They empower you to create organized, efficient, and reusable data structures, enabling you to work with complex data in a structured and modular fashion.

--

--

Hari Perev

Principal Engineer @ Sky | Passionate motorcyclist 🏍 | Code artisan 🖋 | Striving to make a positive impact | Connect @ hatronix.com 🔗