Part of C in 100s

C in 100 Seconds: Structs — Group Your Data | Episode 23

Celest KimCelest Kim

Video: C in 100 Seconds: Structs | Episode 23 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Structs

typedef struct { char name[50]; int age; } Student; groups related data into a named type. Access fields with .. C's version of records — no methods, just data.

Structs let you bundle several values under one name. They're how every non-trivial C program organises data — without them you'd have parallel arrays everywhere.

The basic shape

#include <stdio.h>

typedef struct {
  char name[50];
  int age;
  float gpa;
} Student;

int main() {
  Student s = {"Alice", 20, 3.85};

  printf("Name: %s\n", s.name);
  printf("Age:  %d\n", s.age);
  printf("GPA:  %.2f\n", s.gpa);

  s.age = 21;
  printf("\nUpdated age: %d\n", s.age);

  return 0;
}

struct vs typedef struct

C has two ways to declare:

// Plain struct — must use "struct" prefix when referring
struct Student {
  char name[50];
  int age;
};
struct Student s = {"Alice", 20};

// typedef struct — defines a new type name
typedef struct {
  char name[50];
  int age;
} Student;
Student s = {"Alice", 20};   // no "struct" prefix needed

The typedef form lets you write Student instead of struct Student. Cleaner for everyday use; idiomatic in modern C.

The named-and-typedef'd form lets you both:

typedef struct Student {   // both name AND typedef
  char name[50];
  int age;
  struct Student *next;     // can refer to itself by struct name
} Student;

Useful for self-referential structs (linked lists, trees) — the body needs to reference struct Student before the typedef is complete.

Initialization

Student s = {"Alice", 20, 3.85};   // positional
Student s = { .name = "Alice", .age = 20, .gpa = 3.85 };  // designated (C99+)
Student s = {0};                    // all fields zero
Student s;                          // uninitialized — fields are garbage

Designated initializers (with .field = value) are clearer for many fields. Positional initializers work but are order-dependent.

{0} zero-initializes the whole struct — common idiom.

Accessing fields with .

s.name      // string field
s.age       // int field
s.gpa       // float field
s.age = 21; // assign

Standard dot-notation. s is the struct; name is the field. Same as Python, JavaScript, Java for instance fields.

Sizes and alignment

typedef struct {
  char c;
  int i;
  char c2;
} Mixed;

printf("%lu\n", sizeof(Mixed));   // 12 on most platforms, not 6

The struct is paddedint typically aligns to 4-byte boundaries, so the compiler inserts padding bytes. Layout:

Offset 0: char c    (1 byte)
Offset 1-3: padding (3 bytes)
Offset 4-7: int i   (4 bytes)
Offset 8: char c2   (1 byte)
Offset 9-11: padding (3 bytes for the struct's alignment)
Total: 12 bytes

Reorder for tightness:

typedef struct {
  int i;
  char c;
  char c2;
  // 2 bytes padding at end
} Tighter;   // 8 bytes

For high-density storage, sort fields biggest-to-smallest. Most code doesn't worry about it.

Passing structs

void print_student(Student s) {       // pass by value — COPIES
  printf("%s\n", s.name);
}

void birthday(Student *s) {           // pass by pointer — references
  s->age++;                            // -> for "dereference and access field"
}

print_student(alice);                 // copies the whole struct
birthday(&alice);                     // passes pointer

Pass small structs by value is fine. For large structs, pass by pointer to avoid copying.

s->age is sugar for (*s).age — dereference then access. Episode 24 dives into this.

Returning structs

Student create_student(const char *name, int age) {
  Student s = {0};
  strcpy(s.name, name);
  s.age = age;
  return s;
}

Student alice = create_student("Alice", 20);

Returning structs by value works (copies). Modern compilers often optimize this to "return value optimization" — no actual copy. But if the struct is huge, prefer:

  • Pass an output pointer: void create(Student *out, ...).
  • Return a pointer to heap memory: Student *p = malloc(sizeof(Student));.

Arrays of structs

Student class[3] = {
  {"Alice", 20, 3.85},
  {"Bob", 22, 3.50},
  {"Charlie", 19, 3.95},
};

for (int i = 0; i < 3; i++) {
  printf("%s: %.2f\n", class[i].name, class[i].gpa);
}

class[i] is the i-th student; .name accesses fields.

Nested structs

typedef struct {
  int x;
  int y;
} Point;

typedef struct {
  Point top_left;
  Point bottom_right;
  int color;
} Rectangle;

Rectangle r = {{0, 0}, {10, 5}, 0xFF0000};
printf("%d, %d\n", r.top_left.x, r.bottom_right.y);

Structs containing structs. Field access chains: r.top_left.x.

Comparing structs

Student a = {"Alice", 20, 3.85};
Student b = {"Alice", 20, 3.85};

if (a == b) { ... }   // ERROR — can't compare structs with ==
if (memcmp(&a, &b, sizeof(Student)) == 0) { ... }   // bytewise compare

C doesn't have built-in struct equality. Use memcmp for bytewise compare — but watch out for padding bytes (which contain garbage). For reliable equality, write a per-field comparison.

Common mistakes

Forgetting typedef. struct Student s works; Student s doesn't, unless you typedef.

Returning pointer to local struct. Student* foo() { Student s; return &s; }s is destroyed when foo returns.

Comparing with ==. Doesn't compile (or worse, compares pointers if you forgot *). Use memcmp carefully or per-field.

Padding surprises. sizeof(Mixed) isn't always sum of fields. Reorder for compactness.

Mutating through copy. void func(Student s) { s.age = 99; } modifies the copy, not the caller's struct.

memset on a struct with pointers. memset(&s, 0, sizeof(s)); zeros the bytes — pointers become NULL, ints become 0, but if any field needed special init (like a malloc), that's lost.

What's next

Episode 24: structs and pointers — the arrow operator. Why s->field and (*s).field are the same.

Recap

typedef struct { fields } TypeName; for a record type. Initialise with positional or designated form ({ .field = value }). Access fields with .; through pointer with ->. Padding is automatic — sizeof(struct) may exceed sum of fields. Pass small structs by value, large by pointer. C doesn't compare structs with == — use memcmp or per-field. Self-referential needs the named-typedef form.

Next episode: structs and pointers.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.