// The scattered approach — three unrelated variableschar name[50];
int roll;
float gpa;C Structures: Creating Custom Data Types With struct
What is C Structures? Core Concept & Direct Summary
What is a `struct` in C, how does it encapsulate heterogeneous data members, and how do you define and instantiate structures?
Every data type you've used so far holds one kind of thing. An int is a number. A char is a character. An array holds many values — but all of the same type.
Real-world data isn't like that. A student is a name (string) and an age (int) and a GPA (float). A point is an x-coordinate and a y-coordinate. A product is a name, a price, and a stock count. You can't express "a thing made of these different pieces" with an array — arrays are homogeneous, and reality is not.
Structures solve exactly this. A struct lets you bundle variables of different types together into one named unit — a custom data type that describes a thing rather than a value.
This is the pillar page for our Structures cluster, and it's a genuine turning point in the series: from here on, you'll model problems rather than manipulate variables.
(Suggested image: the hero graphic — a labeled "Student" container holding a char array (name), an int (roll), and a float (gpa), with the whole box treated as one unit named s1)
How Does C Structures Work? In-Depth Mechanics & Architecture
Why Arrays Aren't Enough
Here's the problem structures solve. Suppose you want to store three related facts about a student:
It works for one student. For thirty, you'd need name1, name2, name3... and no way to pass a "student" to a function. Now imagine a function that prints a student — it would need three parameters, and nothing stops a caller from passing mismatched ones:
Would arrays help? Only if everything were the same type:
Structures give related data a name and a shape:
Now s1 is a single thing — it can be passed to functions, stored in arrays, returned from them. And the compiler knows its shape, so member access is checked.
(Suggested image: contrast graphic — left: scattered loose variables; right: the same values boxed together in a labeled struct Student container)
Defining a Structure
Breaking it down:
struct Student— the tag. The type's name isstruct Student(both words — a detail that trips everyone at first, and whichtypedeffixes later on this page).- Members (also called fields) listed inside braces, each with its own type
- A terminating semicolon after the closing brace — required, and one of the most common beginner errors when its absence produces a cryptic message about the next line.
Where to put the definition: before any function that uses it — at the top of your file, or in a header.
Output:
A struct definition creates a type, not a variable — like struct Point is now a blueprint. struct Point p; builds an actual instance from it.
(Suggested image: the definition annotated — tag, members, terminating semicolon — with the semicolon called out as the classic trip-up)
All the Ways to Declare and Initialize
Form 1: Declare, then assign members
Simple, explicit. Works everywhere.
Form 2: Initialize with braces (in declaration order)
Values fill members in the order they're declared. Reorder the members in the definition, and this initializer silently misassigns — a good argument for the designated form next.
Form 3: Designated initializers (C99) — by name
Use this form. It's immune to member reordering, it reads clearly, and unspecified members get zeroed:
Form 4: Define and declare at once
This works, but it's usually avoided: you can't reuse the type elsewhere, and unnamed structs make passing values around unnecessarily hard. Define the type separately, then declare variables of it — that's the pattern that scales.
Form 5: Copy from another struct
This is where structs differ sharply from arrays. Arrays can't be assigned with = (from the pointers vs arrays page), but structs can — this copies every member. And structs of the same type are comparable member by member, though == on structs is still not allowed.
(Suggested image: the five forms as code cards, with Form 3 (designated) marked as recommended)
Accessing Members: The Dot Operator
Output:
The . operator reaches into a struct variable and selects a member. After that, the member behaves like any variable of its type — s1.roll is an int you can do arithmetic on, s1.name is a char array you can pass to strcpy or printf("%s").
⚠️ Notice the strcpy on line 3.s1.name = "Priya"; is a compile error, because name is an array and arrays aren't assignable — the same rule from the arrays cluster, now appearing inside a struct. Two options:
(Suggested image: the dot operator visualized — an arrow from s1.roll into the specific member box inside the struct)
typedef: Making Struct Names Shorter
Writing struct Student everywhere gets tedious. The typedef creates a shorter alias:
Now the type name is just Student:
Two notes:
- The struct tag is gone in this form (
typedef struct { ... } Student;— an anonymous struct given a name by typedef). That's fine and it's the most common modern style. - You can keep the tag if you want both names:
typedef struct Student { ... } Student;— useful when the struct needs to reference itself, as in a linked list node:A self-referential struct must use the tagged form, becauseNodeisn't defined yet at the pointnextis declared. That's a real rule you'll hit the moment you write a linked list.
Recommendation: use typedef from the start. It removes struct noise from every declaration, and it's what most modern C code does.
(Suggested image: "with and without typedef" side-by-side — the same declarations, showing how much shorter and cleaner the typedef version is)
Pointers to Structs and the Arrow Operator
Passing and referring to structs typically happens through pointers (for efficiency — a large struct copied by value is expensive). But then member access needs different syntax, because . has higher precedence than *:
(*ptr).x is correct but ugly, so C provides a shortcut: the arrow operator->.
When to use which:
Read -> as "points to."student->gpa means "the gpa of the struct that student points to." Once you internalize that, the syntax stops feeling arbitrary — it exists precisely because (*ptr).member is error-prone to type.
(Suggested image: the same struct shown with both a variable name and a pointer, showing . access vs -> access side by side)
Arrays of Structs
This is where structures earn their keep — a collection of records:
Sample Run:
Look what's happening in this program — it's the kind of code you couldn't sanely write before this page:
- Each element of the array is a complete record —
class[i]holds a name, a roll number, three subject marks, and a computed average, all together. - Structs nest. A struct member can be an array (
marks[3]) or achararray (name) — members are full-fledged variables of any type. class[i].marks[j]— indexing the array of structs, then reaching into a member, then indexing that member. Nested access composes naturally, and the code reads left to right: "the marks of the ith student."- The derived field.
averageis computed once during input and stored, so the report loop doesn't recalculate it. Storing derived values in the struct is a legitimate design choice when they're expensive or used often.
Notice also that this page's material from earlier in the series is doing the work: fgets + strcspn for safe line input, the getchar() flush for the scanf/fgets mixing problem, and printf width specifiers to make the table align. Structures don't replace the earlier tools — they organize them.
Passing Structs to Functions
Two ways, with a real performance difference.
By value — the whole struct is copied:
Fine for small structs, and safe (the function can't alter your original). But if the struct holds a 200-character buffer plus other fields, every call copies all of it.
By pointer — just the address travels:
Which to use:
The professional default: pass struct pointers with const for read-only access. It's the same discipline as the array-parameter and pointer-parameter tutorials, applied to structs. By-value passing is a reasonable exception for genuinely small structs (two or three ints), where a pointer would add indirection for no benefit.
(Suggested image: by-value (a large struct duplicated into the function) vs by-pointer (one arrow) side by side, with the memory cost labeled)
Nested Structures
A struct can contain other structs as members — modeling composition:
Output:
Nested access just keeps chaining dots: p.birthDate.day reads as "the day of the birthDate of p." That's why structures feel natural — the syntax mirrors the hierarchy you're describing.
Note the anonymous inner struct for address — legal when you don't need to name that type elsewhere. Handy for one-off groupings, and it keeps the type namespace clean.
The One Big Surprise: Structs Aren't Just Stuck Together
Here's something worth knowing early, because it explains a whole category of confusion later:
Structs are padded. The compiler inserts invisible bytes between members so each one lands on a memory boundary its type prefers (4-byte ints align to 4-byte boundaries). So sizeof(Mixed) is typically 8, not the 5 you'd expect from adding 1 + 4.
Why this matters:
sizeofon a struct includes padding — never assume it equals the sum of its members- Writing a struct to a file with
fwritewrites padding bytes too, which may differ between compilers/platforms — so struct file formats aren't portable - Member order affects the total size (putting larger members first can reduce padding)
This gets its full treatment in the structures cluster later. For now, the headline: sizeof(struct) ≥ the sum of its members.
Common Structure Mistakes
- Forgetting the semicolon after the closing brace — the error message often points at the next line, sending you hunting in the wrong place.
- Writing
struct Studentbut declaring justStudent(or vice versa) — if you typedef'd it, drop thestruct; if you didn't, keep it. Choose one approach and be consistent. - Using
=on achararray member —s.name = "Priya";is a compile error. Usestrcpyor brace initialization. - Using
.on a struct pointer —ptr.xdoesn't compile. Pointers use->, or(*ptr).x. - Comparing structs with
==— not allowed in C. Compare member by member (and usestrcmpforchararrays inside). - Forgetting that arrays in structs aren't copyable by assignment — struct assignment
q = pcopies everything including array members. Butq.name = p.namedoesn't work. - Assuming
sizeofequals the sum of members — padding. Checksizeofif the size matters. - Passing large structs by value in hot code — silent performance cost from all that copying. Pass pointers.
- Reading an uninitialized struct — uninitialized members hold garbage, just like uninitialized variables. Initialize with braces or designated initializers.
- Trying to
printfa whole struct — there's no format specifier for a struct. Print its members individually.
(Suggested image: "10 Struct Mistakes" checklist graphic — consistent with the series, with #1 and #4 highlighted)
Final Thoughts
Structures are the moment C stops being a language of values and starts being a language of things. An int is a number; a struct Student is a student. That shift — from primitive types to domain types — is what makes larger programs comprehensible, because the code starts describing the problem instead of the machine.
The core material is compact: define with struct, name your type with typedef, reach members with ., reach members through pointers with ->, initialize with designated initializers, and pass large structs by const pointer. That's the working vocabulary, and it covers nearly everything you'll write.
Two bigger ideas to carry forward: structures compose (structs containing structs, arrays of structs, pointers to structs), and your own types can be as natural to use as built-in ones. Once Student and Point and Date are types in your program, the code starts reading like the problem statement — which is the whole goal.
Next in this cluster: typedef and struct design in depth, then nested structures, arrays of structs, struct pointers, and self-referential structures (the gateway to linked lists).
Runnable C Code Example & Practical Implementation
#include <stdio.h>
typedef struct {
int id;
char title[64];
double price;
}
Book;
int main() {
Book myBook = {
101, "The C Programming Language", 45.50
};
printf("Book ID: %d\
", myBook.id);
printf("Title: %s\
", myBook.title);
printf("Price: $%.2f\
", myBook.price);
printf("Struct Size:%zu bytes\
", sizeof(Book));
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
double, int, short, char) to minimize compiler padding waste.Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: What is structure padding and alignment in C?
Q: Can structures be compared directly using `==` in C?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.