A struct in C groups items of possibly different types into one composite type. An array holds many of the same thing; a struct holds a few different things. This is how you represent records, linked-structure nodes, and anything “object-like”.

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;
struct Student s2 = {.name = "Alice", .section = 'A'};   // designated initializer

The members are accessed with the dot operator:

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

Full walkthrough — defining, declaring, populating, accessing:

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

When you have a uniform collection of records, make the struct the array element:

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. You’ll see the arrow form everywhere struct pointers show up; basically nobody writes (*p).x.

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). Most CPUs penalize or fault on misaligned accesses, hence the rule.

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.

Building data structures

Every non-trivial C data structure is built on structs. A linked list node is struct node { int value; struct node *next; }; — data plus a pointer to the next struct. Trees, graphs, hash table buckets, queue nodes: all structs with pointer fields.

See also Pointer arithmetic and Dynamic memory allocation.