A struct in C groups items of possibly different types into a single composite type. Where an array holds many of the same thing, a struct holds a few of different things — the building block for representing records, nodes in linked structures, and any “object-like” data.

struct Student {
    char name[50];
    int  studentNumber;
    char section;
};

This declares a typestruct Student — but doesn’t allocate memory. To use it, declare a variable:

struct Student s1;          // a single Student
struct Student s2 = {.name = "Alice", .section = 'A'};   // designated initializer

The members are accessed with the dot operator:

s1.studentNumber = 12345;
strcpy(s1.name, "Bob");

For a more detailed walkthrough, see how a complete struct gets defined, declared, populated, and accessed:

Typedef

typedef creates an alias for an existing type. With structs it’s the standard way to avoid having to write struct everywhere:

typedef struct {
    char name[50];
    int  class;
    char section;
} Student;        // Student is now a type alias
 
Student s1, s2;   // no "struct" prefix needed

Behind the scenes there’s still a struct; typedef just gives it a shorter name.

typedef works on any type — not just structs:

typedef int studentNumberType;
studentNumberType studentNumber1;     // really just an int, but more readable

Nested structs

A struct field can itself be a struct:

typedef struct {
    int   imag;
    float real;
} Complex;
 
struct Number {
    int     flags;
    Complex phase;     // struct inside struct
} num1;
 
num1.phase.real = 3.14;   // dot-chain to nested member

Enums

enum defines a set of named integer constants. Useful for state codes, options, etc.:

enum Color { RED, GREEN, BLUE };  // RED=0, GREEN=1, BLUE=2
 
enum Day { Monday = 1, Tuesday, Wednesday, ... };  // Monday=1, Tuesday=2, ...

By default the constants start at 0; you can override starting values explicitly. Combined with typedef:

typedef enum { RED, GREEN, BLUE } Color;
Color c = GREEN;

Structs as array elements

Structs make perfect array elements when you have a uniform collection of records:

typedef struct {
    int x, y;
} Point;
 
Point vertices[100];
vertices[4].x = 23;
vertices[4].y = 18;

Each array element is a complete struct; vertices[4].x accesses the x field of the fifth Point.

Pointers to structs

A pointer to a struct lets you pass the struct around without copying it:

Point a = {23, 18};
Point *p = &a;
(*p).x = 34;     // dereference and access field
p->x = 34;       // shortcut: arrow operator

The -> operator is shorthand for “dereference and access field” — p->x is (*p).x. The arrow form is universal in C code that uses struct pointers.

Pointers to pointers

Less common, but you’ll see it for functions that need to modify a pointer (e.g., update the head of a linked list):

void allocateInt(int **p) {
    *p = malloc(sizeof(int));
}
 
int *myPtr = NULL;
allocateInt(&myPtr);   // now myPtr points to the new int

The double-pointer is “address of a pointer.” Inside the function, *p is the pointer, and assigning to it changes what the caller’s pointer points at.

This pattern shows up everywhere in Linked list operations — to insert at the head of a list, you need to modify the caller’s head pointer, which requires node **head.

Alignment and padding

Struct fields are not packed contiguously in memory. The compiler inserts padding so each field starts on an address that’s a multiple of its alignment requirement — typically the field’s size up to the platform word width (8 bytes on 64-bit). This matters because most CPUs penalize or fault on misaligned accesses.

struct Bad {
    char  a;       // offset 0, size 1
    // 3 bytes padding here so the int starts at offset 4
    int   b;       // offset 4, size 4
    char  c;       // offset 8, size 1
    // 3 bytes trailing padding so sizeof(Bad) is a multiple of int's alignment
};
// sizeof(struct Bad) == 12, not 6.
 
struct Good {
    int   b;       // offset 0
    char  a;       // offset 4
    char  c;       // offset 5
    // 2 bytes trailing padding
};
// sizeof(struct Good) == 8.

Reordering fields from largest alignment to smallest minimizes padding. For embedded code where every byte matters, or when defining a struct that mirrors a hardware register layout, you also need to know about __attribute__((packed)) (GCC/Clang) or #pragma pack (MSVC), which removes padding entirely — at the cost of slow or trapping unaligned access on some platforms.

Why structs matter

Structs are the foundation of every non-trivial C data structure. A linked list node is struct node { int value; struct node *next; }; — a struct holding data plus a pointer to the next struct. Trees, graphs, hash table buckets, queue nodes — all built from structs with pointer fields.

For the C basics that complement structs, see Pointer arithmetic and Dynamic memory allocation.