A structure defines one object type whose named members can have different types. It is the normal C tool for records such as coordinates, protocol headers, configuration values, and domain entities. Each structure object contains storage for all of its members, with possible padding inserted to satisfy alignment requirements.
This lesson covers declaration, initialization, nested structures, member access, function interfaces, copying, padding, and ownership. You will learn why a copied structure may still share pointed-to data and why raw structure bytes are usually the wrong format for files or networks.
A tag such as struct Student names the structure type. Members are declared inside the definition and accessed with the dot operator on an object. A typedef can provide an alias, but it does not create a second representation. Use names that reveal the domain rather than hiding whether a type owns resources.
Designated initializers bind values to member names and remain readable when members have the same scalar type. Members not explicitly initialized in an initializer are zero-initialized. Assignment after declaration does not automatically initialize untouched members, so establish a valid state before passing the object onward.
// Define a structure
struct Student {
char name[50];
int age;
float gpa;
};
// Declare a variable
struct Student s1;
// Access members with dot operator
s1.age = 20;
strcpy(s1.name, "Alice");
// Initialize at declaration
struct Student s2 = {"Bob", 22, 3.8f};
A structure member can itself be a structure or array. Nest by value when the child has the same lifetime and is logically part of the parent. Use a pointer when the relationship is optional, shared, dynamically sized, or managed elsewhere, and document who owns the pointed-to object.
#include <stdio.h>
typedef struct { int year, month, day; } Date;
typedef struct {
int id;
char name[32];
Date joined;
} Employee;
int main(void) {
Employee employee = {
.id = 17,
.name = "Mira",
.joined = {.year = 2026, .month = 7, .day = 12}
};
printf("%d %s %04d-%02d-%02d\n", employee.id, employee.name,
employee.joined.year, employee.joined.month, employee.joined.day);
return 0;
}
17 Mira 2026-07-12
Use object.member when you have a structure object and pointer->member when you have a pointer to one. The arrow expression is equivalent to (*pointer).member with the required grouping. Check whether a pointer may be null before dereferencing it.
Pass a pointer when a function should modify the caller object or avoid copying a large record. Pass a pointer to const when the function only reads it. Passing a small structure by value creates an independent memberwise copy and can make ownership clearer when every member is self-contained.
struct Student *ptr = &s1;
printf("%s", ptr->name); // same as (*ptr).name
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
float gpa;
char grade;
};
void printStudent(struct Student s) {
printf("Name: %s\n", s.name);
printf("Age: %d\n", s.age);
printf("GPA: %.2f\n", s.gpa);
printf("Grade: %c\n", s.grade);
}
int main() {
struct Student s1;
strcpy(s1.name, "Alice");
s1.age = 20;
s1.gpa = 3.85f;
s1.grade = 'A';
// Initialize at declaration
struct Student s2 = {"Bob", 22, 3.2f, 'B'};
printf("--- Student 1 ---\n");
printStudent(s1);
printf("\n--- Student 2 ---\n");
printStudent(s2);
printf("\nSize of struct Student: %zu bytes\n", sizeof(struct Student));
return 0;
}
#include <stdio.h>
#include <string.h>
struct Employee {
char name[50];
int id;
float salary;
};
int main() {
// Array of structures
struct Employee team[3] = {
{"Alice", 101, 75000.0f},
{"Bob", 102, 68000.0f},
{"Carol", 103, 82000.0f}
};
printf("%-10s %5s %10s\n", "Name", "ID", "Salary");
printf("---------------------------\n");
for (int i = 0; i < 3; i++) {
printf("%-10s %5d %10.2f\n",
team[i].name, team[i].id, team[i].salary);
}
// Pointer to struct - use arrow operator ->
struct Employee *ptr = &team[0];
printf("\nFirst employee via pointer:\n");
printf("Name: %s, Salary: %.2f\n", ptr->name, ptr->salary);
ptr++; // move to next struct
printf("Second employee via pointer:\n");
printf("Name: %s, ID: %d\n", ptr->name, ptr->id);
return 0;
}
#include <stdio.h>
#include <string.h>
// typedef lets you use the type without 'struct' keyword
typedef struct {
int day;
int month;
int year;
} Date;
typedef struct {
char name[50];
Date birthdate; // nested struct
float salary;
} Person;
int main() {
Person p;
strcpy(p.name, "Alice");
p.birthdate.day = 15;
p.birthdate.month = 6;
p.birthdate.year = 1995;
p.salary = 60000.0f;
printf("Name: %s\n", p.name);
printf("Birthdate: %02d/%02d/%d\n",
p.birthdate.day, p.birthdate.month, p.birthdate.year);
printf("Salary: %.2f\n", p.salary);
// No need to write 'struct Person' - just 'Person'
Person p2 = {"Bob", {20, 3, 1990}, 55000.0f};
printf("\nName: %s, Born: %d\n", p2.name, p2.birthdate.year);
return 0;
}
Compilers may insert padding between members and after the final member so each field has suitable alignment and arrays of the structure work correctly. sizeof(struct Type) therefore may exceed the sum of member sizes. Reordering members can change size, but optimize layout only when measurement or an external ABI requires it.
Do not write a structure object directly to a portable file or send its bytes over a network. Padding bytes, integer widths, byte order, floating representation, and compiler layout can differ. Serialize each field into a defined format and validate values while decoding.
#include <stddef.h>
#include <stdio.h>
typedef struct {
char active;
int count;
double total;
} Report;
int main(void) {
printf("size=%zu active=%zu count=%zu total=%zu\n",
sizeof(Report), offsetof(Report, active),
offsetof(Report, count), offsetof(Report, total));
return 0;
}
The exact numbers are implementation-dependent. offsetof reveals the actual member offsets selected by the current implementation.
Structure assignment and pass-by-value copy each member. Embedded arrays are copied as part of the object. Pointer members copy only the address, so the original and copy refer to the same allocation. Freeing or modifying shared pointee data through one copy affects the other and can cause double-free or dangling-pointer bugs.
For an owning structure, provide explicit initialize, clone, and destroy functions. Initialize every pointer to null or valid owned storage, make cleanup safe for partially initialized objects, and document whether functions borrow, take, or return ownership.
Try this next
0 of 3 completed
No. typedef adds an alias; it does not change member layout or alignment.
Yes. Returning by value copies the structure result, subject to normal lifetime and ownership rules for its members.
Padding bytes may differ even when all member values are equal, and pointer members need semantic rather than address comparison.
Explore 500+ free tutorials across 20+ languages and frameworks.