C — Structures, Unions & Enums
Structures, unions, and enums are the primary tools for creating custom types in C. Together they let you model real-world data — records, variant types, and named constants — without leaving the language. This page covers declaration, initialization, memory layout, and practical patterns for each.
A struct groups variables of different types under a single name. Each member occupies its own storage. You declare a struct type and then create instances of it:
| 1 | struct Point { |
| 2 | double x; |
| 3 | double y; |
| 4 | }; |
| 5 | |
| 6 | int main(void) { |
| 7 | struct Point p1 = { 3.0, 4.0 }; /* positional init */ |
| 8 | struct Point p2 = { .x = 1.0, .y = 2.0 }; /* designated init (C99) */ |
| 9 | struct Point p3 = { 5.0 }; /* y defaults to 0.0 */ |
| 10 | struct Point p4 = { 0 }; /* both members zeroed */ |
| 11 | |
| 12 | printf("p1 = (%.1f, %.1f)\n", p1.x, p1.y); |
| 13 | printf("p2 = (%.1f, %.1f)\n", p2.x, p2.y); |
| 14 | return 0; |
| 15 | } |
info
The dot operator (.) accesses individual members of a struct. Members can be read and written like any other variable:
| 1 | struct Employee { |
| 2 | char name[64]; |
| 3 | int id; |
| 4 | double salary; |
| 5 | }; |
| 6 | |
| 7 | void print_employee(const struct Employee *e) { |
| 8 | printf("ID: %d | Name: %s | Salary: $%.2f\n", |
| 9 | e->id, e->name, e->salary); |
| 10 | } |
| 11 | |
| 12 | int main(void) { |
| 13 | struct Employee emp; |
| 14 | |
| 15 | strcpy(emp.name, "Alice Chen"); |
| 16 | emp.id = 1042; |
| 17 | emp.salary = 95000.00; |
| 18 | |
| 19 | print_employee(&emp); |
| 20 | return 0; |
| 21 | } |
sizeof returns the total size of a struct including padding inserted by the compiler for alignment. Understanding padding is critical for binary I/O, networking, and embedded systems:
| 1 | #include <stdio.h> |
| 2 | |
| 3 | struct Bad { |
| 4 | char a; /* 1 byte + 3 padding */ |
| 5 | int b; /* 4 bytes */ |
| 6 | char c; /* 1 byte + 3 padding */ |
| 7 | }; |
| 8 | |
| 9 | struct Good { |
| 10 | int b; /* 4 bytes */ |
| 11 | char a; /* 1 byte */ |
| 12 | char c; /* 1 byte + 2 padding */ |
| 13 | }; |
| 14 | |
| 15 | struct Packed { |
| 16 | char a; |
| 17 | int b; |
| 18 | char c; |
| 19 | } __attribute__((packed)); |
| 20 | |
| 21 | int main(void) { |
| 22 | printf("sizeof(Bad) = %zu\n", sizeof(struct Bad)); /* 12 */ |
| 23 | printf("sizeof(Good) = %zu\n", sizeof(struct Good)); /* 8 */ |
| 24 | printf("sizeof(Packed)= %zu\n", sizeof(struct Packed)); /* 6 */ |
| 25 | return 0; |
| 26 | } |
warning
Unlike arrays, structs can be assigned with the= operator. This performs a shallow member-wise copy. Two structs with pointer members will share the pointed-to memory after assignment:
| 1 | struct Buffer { |
| 2 | size_t len; |
| 3 | char *data; |
| 4 | }; |
| 5 | |
| 6 | int main(void) { |
| 7 | struct Buffer a = { 5, malloc(5) }; |
| 8 | memcpy(a.data, "hello", 5); |
| 9 | |
| 10 | struct Buffer b = a; /* shallow copy */ |
| 11 | b.len = 10; /* independent */ |
| 12 | b.data[0] = 'H'; /* shared — modifies a too! */ |
| 13 | |
| 14 | printf("a.data = %.5s\n", a.data); /* "Hello" */ |
| 15 | printf("b.data = %.5s\n", b.data); /* "Hello" */ |
| 16 | |
| 17 | free(a.data); |
| 18 | /* free(b.data); — double free! Both point to same memory */ |
| 19 | |
| 20 | return 0; |
| 21 | } |
best practice
Structs can be passed by value or by pointer. Passing by value copies the entire struct onto the stack — expensive for large structs. Passing by pointer is faster and allows the function to modify the original:
| 1 | struct Vec3 { float x, y, z; }; |
| 2 | |
| 3 | /* Pass by value — entire struct copied */ |
| 4 | float length_by_value(struct Vec3 v) { |
| 5 | return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); |
| 6 | } |
| 7 | |
| 8 | /* Pass by pointer — only 8 bytes copied (the pointer) */ |
| 9 | float length_by_ptr(const struct Vec3 *v) { |
| 10 | return sqrtf(v->x * v->x + v->y * v->y + v->z * v->z); |
| 11 | } |
| 12 | |
| 13 | /* Pass by pointer — modifies in place */ |
| 14 | void normalize(struct Vec3 *v) { |
| 15 | float len = length_by_ptr(v); |
| 16 | if (len > 0.0f) { |
| 17 | v->x /= len; |
| 18 | v->y /= len; |
| 19 | v->z /= len; |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | int main(void) { |
| 24 | struct Vec3 v = { 3.0f, 4.0f, 0.0f }; |
| 25 | printf("length = %.2f\n", length_by_value(v)); /* 5.00 */ |
| 26 | printf("length = %.2f\n", length_by_ptr(&v)); /* 5.00 */ |
| 27 | normalize(&v); |
| 28 | printf("normalized = (%.2f, %.2f, %.2f)\n", v.x, v.y, v.z); |
| 29 | return 0; |
| 30 | } |
Functions can return structs by value. The compiler typically uses a hidden pointer to the caller's storage (the "hidden return" convention) to avoid copying on return. Large structs should still be returned via pointer for clarity:
| 1 | struct Rect { |
| 2 | double x, y, width, height; |
| 3 | }; |
| 4 | |
| 5 | struct Rect make_rect(double x, double y, double w, double h) { |
| 6 | struct Rect r = { .x = x, .y = y, .width = w, .height = h }; |
| 7 | return r; |
| 8 | } |
| 9 | |
| 10 | double area(struct Rect r) { |
| 11 | return r.width * r.height; |
| 12 | } |
| 13 | |
| 14 | int main(void) { |
| 15 | struct Rect box = make_rect(10.0, 20.0, 5.0, 3.0); |
| 16 | printf("area = %.1f\n", area(box)); /* 15.0 */ |
| 17 | return 0; |
| 18 | } |
Arrays of structs store elements contiguously in memory, making them cache-friendly. This is the typical pattern for collections of records:
| 1 | struct Student { |
| 2 | char name[32]; |
| 3 | float gpa; |
| 4 | }; |
| 5 | |
| 6 | int main(void) { |
| 7 | struct Student class[] = { |
| 8 | { "Alice", 3.9f }, |
| 9 | { "Bob", 3.5f }, |
| 10 | { "Carol", 3.8f }, |
| 11 | { "Dave", 3.2f }, |
| 12 | }; |
| 13 | int n = sizeof(class) / sizeof(class[0]); |
| 14 | |
| 15 | /* find highest GPA */ |
| 16 | float max_gpa = 0.0f; |
| 17 | int best = 0; |
| 18 | for (int i = 0; i < n; i++) { |
| 19 | if (class[i].gpa > max_gpa) { |
| 20 | max_gpa = class[i].gpa; |
| 21 | best = i; |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | printf("Top student: %s (%.1f)\n", class[best].name, max_gpa); |
| 26 | return 0; |
| 27 | } |
Structs can contain other structs as members. This enables hierarchical data modeling — a person has an address, an address has a city, etc.
| 1 | struct Date { |
| 2 | int year, month, day; |
| 3 | }; |
| 4 | |
| 5 | struct Person { |
| 6 | char name[64]; |
| 7 | struct Date birth; |
| 8 | struct { |
| 9 | char street[128]; |
| 10 | char city[64]; |
| 11 | int zip; |
| 12 | } address; /* anonymous nested struct */ |
| 13 | }; |
| 14 | |
| 15 | int main(void) { |
| 16 | struct Person p = { |
| 17 | .name = "Diana Prince", |
| 18 | .birth = { .year = 1975, .month = 3, .day = 1 }, |
| 19 | .address = { |
| 20 | .street = "123 Justice Lane", |
| 21 | .city = "Gateway City", |
| 22 | .zip = 90210 |
| 23 | } |
| 24 | }; |
| 25 | |
| 26 | printf("%s was born on %d-%02d-%02d\n", |
| 27 | p.name, p.birth.year, p.birth.month, p.birth.day); |
| 28 | printf("Lives in %s, %d\n", p.address.city, p.address.zip); |
| 29 | return 0; |
| 30 | } |
When you have a pointer to a struct, the arrow operator (->) dereferences and accesses in one step. It is syntactic sugar for(*ptr).member:
| 1 | struct Config { |
| 2 | int width; |
| 3 | int height; |
| 4 | int fullscreen; |
| 5 | }; |
| 6 | |
| 7 | void apply_config(struct Config *cfg) { |
| 8 | /* These are equivalent: */ |
| 9 | int w1 = (*cfg).width; /* explicit dereference */ |
| 10 | int w2 = cfg->width; /* arrow operator (preferred) */ |
| 11 | |
| 12 | cfg->width = 1920; |
| 13 | cfg->height = 1080; |
| 14 | cfg->fullscreen = 1; |
| 15 | } |
| 16 | |
| 17 | int main(void) { |
| 18 | struct Config cfg = { 0 }; |
| 19 | apply_config(&cfg); |
| 20 | printf("%dx%d fullscreen=%d\n", cfg.width, cfg.height, cfg.fullscreen); |
| 21 | return 0; |
| 22 | } |
typedef eliminates the need to repeat struct everywhere. It is the idiomatic way to define custom types in C:
| 1 | typedef struct { |
| 2 | double x, y; |
| 3 | } Vec2; |
| 4 | |
| 5 | typedef struct Vec3 { |
| 6 | double x, y, z; |
| 7 | } Vec3; |
| 8 | |
| 9 | Vec2 vec2_add(Vec2 a, Vec2 b) { |
| 10 | return (Vec2){ a.x + b.x, a.y + b.y }; |
| 11 | } |
| 12 | |
| 13 | Vec3 vec3_add(Vec3 a, Vec3 b) { |
| 14 | return (Vec3){ a.x + b.x, a.y + b.y, a.z + b.z }; |
| 15 | } |
| 16 | |
| 17 | int main(void) { |
| 18 | Vec2 p = { 1.0, 2.0 }; |
| 19 | Vec2 q = { 3.0, 4.0 }; |
| 20 | Vec2 r = vec2_add(p, q); |
| 21 | printf("(%.1f, %.1f)\n", r.x, r.y); /* (4.0, 6.0) */ |
| 22 | return 0; |
| 23 | } |
A struct can contain a pointer to itself. This is the foundation of linked data structures — lists, trees, graphs. The pointer is finite in size, unlike embedding the struct directly (which would be infinitely recursive):
| 1 | typedef struct Node { |
| 2 | int data; |
| 3 | struct Node *next; /* self-referential pointer */ |
| 4 | } Node; |
| 5 | |
| 6 | Node *push_front(Node *head, int value) { |
| 7 | Node *new_node = malloc(sizeof(Node)); |
| 8 | new_node->data = value; |
| 9 | new_node->next = head; |
| 10 | return new_node; |
| 11 | } |
| 12 | |
| 13 | void print_list(const Node *head) { |
| 14 | for (const Node *cur = head; cur != NULL; cur = cur->next) { |
| 15 | printf("%d -> ", cur->data); |
| 16 | } |
| 17 | printf("NULL\n"); |
| 18 | } |
| 19 | |
| 20 | void free_list(Node *head) { |
| 21 | while (head) { |
| 22 | Node *tmp = head; |
| 23 | head = head->next; |
| 24 | free(tmp); |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | int main(void) { |
| 29 | Node *list = NULL; |
| 30 | list = push_front(list, 30); |
| 31 | list = push_front(list, 20); |
| 32 | list = push_front(list, 10); |
| 33 | print_list(list); /* 10 -> 20 -> 30 -> NULL */ |
| 34 | free_list(list); |
| 35 | return 0; |
| 36 | } |
The C standard allows the compiler to insert padding bytes between and after struct members to satisfy alignment requirements. You can inspect layout withoffsetof and control it with compiler extensions:
| 1 | #include <stdio.h> |
| 2 | #include <stddef.h> |
| 3 | |
| 4 | struct Packed { |
| 5 | char a; |
| 6 | int b; |
| 7 | char c; |
| 8 | } __attribute__((packed)); |
| 9 | |
| 10 | int main(void) { |
| 11 | printf("sizeof = %zu\n", sizeof(struct Packed)); |
| 12 | printf("offset a = %zu\n", offsetof(struct Packed, a)); |
| 13 | printf("offset b = %zu\n", offsetof(struct Packed, b)); |
| 14 | printf("offset c = %zu\n", offsetof(struct Packed, c)); |
| 15 | /* sizeof = 6, offset b = 1 (no padding) */ |
| 16 | return 0; |
| 17 | } |
MSVC uses #pragma pack instead of__attribute__((packed)):
| 1 | #pragma pack(push, 1) |
| 2 | struct PackedMSVC { |
| 3 | char a; |
| 4 | int b; |
| 5 | char c; |
| 6 | }; |
| 7 | #pragma pack(pop) |
warning
A union overlaps all members at the same memory address. Only one member is valid at any time. The size of a union equals the size of its largest member:
| 1 | union Value { |
| 2 | int i; |
| 3 | float f; |
| 4 | char s[16]; |
| 5 | }; |
| 6 | |
| 7 | int main(void) { |
| 8 | union Value v; |
| 9 | printf("sizeof(union Value) = %zu\n", sizeof(union Value)); /* 16 */ |
| 10 | |
| 11 | v.i = 42; |
| 12 | printf("i = %d\n", v.i); |
| 13 | |
| 14 | v.f = 3.14f; |
| 15 | printf("f = %.2f\n", v.f); /* valid */ |
| 16 | /* printf("i = %d\n", v.i); */ /* undefined — last write was .f */ |
| 17 | |
| 18 | strcpy(v.s, "hello"); |
| 19 | printf("s = %s\n", v.s); |
| 20 | return 0; |
| 21 | } |
best practice
| Feature | struct | union |
|---|---|---|
| Memory | Sum of all members + padding | Size of largest member |
| Access | All members simultaneously | One member at a time |
| Use case | Aggregating related data | Variant types, type punning |
| Assignment | Copies all members | Copies storage (largest member) |
Unions are indispensable for type punning, implementing variant types, and saving memory in embedded systems:
| 1 | #include <stdint.h> |
| 2 | |
| 3 | /* Type punning: reinterpret bytes as float */ |
| 4 | float bytes_to_float(uint8_t bytes[4]) { |
| 5 | union { |
| 6 | float f; |
| 7 | uint8_t b[4]; |
| 8 | } u; |
| 9 | u.b[0] = bytes[0]; |
| 10 | u.b[1] = bytes[1]; |
| 11 | u.b[2] = bytes[2]; |
| 12 | u.b[3] = bytes[3]; |
| 13 | return u.f; |
| 14 | } |
| 15 | |
| 16 | /* Variant type with tag */ |
| 17 | typedef enum { TAG_INT, TAG_FLOAT, TAG_STR } TypeTag; |
| 18 | |
| 19 | typedef struct { |
| 20 | TypeTag tag; |
| 21 | union { |
| 22 | int i; |
| 23 | float f; |
| 24 | char s[32]; |
| 25 | } value; |
| 26 | } Variant; |
| 27 | |
| 28 | void print_variant(const Variant *v) { |
| 29 | switch (v->tag) { |
| 30 | case TAG_INT: printf("int: %d\n", v->value.i); break; |
| 31 | case TAG_FLOAT: printf("float: %f\n", v->value.f); break; |
| 32 | case TAG_STR: printf("str: %s\n", v->value.s); break; |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | int main(void) { |
| 37 | Variant v1 = { .tag = TAG_INT, .value.i = 42 }; |
| 38 | Variant v2 = { .tag = TAG_FLOAT, .value.f = 3.14f }; |
| 39 | Variant v3 = { .tag = TAG_STR }; |
| 40 | strcpy(v3.value.s, "hello"); |
| 41 | |
| 42 | print_variant(&v1); |
| 43 | print_variant(&v2); |
| 44 | print_variant(&v3); |
| 45 | return 0; |
| 46 | } |
Enums create named integer constants. They improve readability and enable compiler warnings for unhandled cases in switch statements:
| 1 | enum Color { |
| 2 | RED, /* 0 */ |
| 3 | GREEN, /* 1 */ |
| 4 | BLUE, /* 2 */ |
| 5 | YELLOW = 10, |
| 6 | CYAN, /* 11 */ |
| 7 | MAGENTA /* 12 */ |
| 8 | }; |
| 9 | |
| 10 | enum Status { |
| 11 | OK = 0, |
| 12 | ERR_IO = -1, |
| 13 | ERR_MEM = -2, |
| 14 | ERR_TIMEOUT = -3 |
| 15 | }; |
| 16 | |
| 17 | int main(void) { |
| 18 | enum Color c = GREEN; |
| 19 | switch (c) { |
| 20 | case RED: printf("Red\n"); break; |
| 21 | case GREEN: printf("Green\n"); break; |
| 22 | case BLUE: printf("Blue\n"); break; |
| 23 | case YELLOW: printf("Yellow\n"); break; |
| 24 | case CYAN: printf("Cyan\n"); break; |
| 25 | case MAGENTA: printf("Magenta\n"); break; |
| 26 | } |
| 27 | |
| 28 | printf("GREEN = %d\n", GREEN); /* 1 */ |
| 29 | return 0; |
| 30 | } |
Enums are perfect for representing states, error codes, and flag sets. Combined with bitwise operations, they enable compact flag systems:
| 1 | /* Bit flags — values are powers of 2 */ |
| 2 | enum FilePerm { |
| 3 | PERM_READ = 1 << 0, /* 1 */ |
| 4 | PERM_WRITE = 1 << 1, /* 2 */ |
| 5 | PERM_EXECUTE = 1 << 2, /* 4 */ |
| 6 | PERM_APPEND = 1 << 3, /* 8 */ |
| 7 | }; |
| 8 | |
| 9 | int main(void) { |
| 10 | int perm = PERM_READ | PERM_WRITE; /* combined flags */ |
| 11 | |
| 12 | if (perm & PERM_READ) printf("Readable\n"); |
| 13 | if (perm & PERM_WRITE) printf("Writable\n"); |
| 14 | if (perm & PERM_EXECUTE) printf("Executable\n"); /* no */ |
| 15 | |
| 16 | perm |= PERM_EXECUTE; /* add flag */ |
| 17 | perm &= ~PERM_WRITE; /* remove flag */ |
| 18 | printf("New perm: %d\n", perm); /* 5 (READ | EXECUTE) */ |
| 19 | return 0; |
| 20 | } |
info
typedef creates an alias for any type. It is heavily used with structs (covered above) but works with all types — pointers, function pointers, and platform-specific sizes:
| 1 | #include <stdint.h> |
| 2 | |
| 3 | typedef unsigned char byte; |
| 4 | typedef int32_t i32; |
| 5 | typedef uint64_t u64; |
| 6 | typedef const char* cstring; |
| 7 | |
| 8 | typedef int (*Comparator)(const void*, const void*); |
| 9 | |
| 10 | int cmp_int(const void *a, const void *b) { |
| 11 | return (*(const int*)a) - (*(const int*)b); |
| 12 | } |
| 13 | |
| 14 | int main(void) { |
| 15 | byte buffer[256] = { 0 }; |
| 16 | i32 value = -42; |
| 17 | u64 big = 18446744073709551615ULL; |
| 18 | |
| 19 | int arr[] = { 5, 2, 8, 1, 9 }; |
| 20 | int n = sizeof(arr) / sizeof(arr[0]); |
| 21 | |
| 22 | qsort(arr, n, sizeof(int), cmp_int); |
| 23 | |
| 24 | for (i32 i = 0; i < n; i++) { |
| 25 | printf("%d ", arr[i]); |
| 26 | } |
| 27 | printf("\n"); |
| 28 | return 0; |
| 29 | } |
C11 allows anonymous structs and unions. Members of the anonymous member are accessed directly as if they belonged to the outer struct, reducing verbosity:
| 1 | struct Record { |
| 2 | int type; |
| 3 | union { /* anonymous union */ |
| 4 | int i; |
| 5 | double d; |
| 6 | char s[32]; |
| 7 | }; |
| 8 | }; |
| 9 | |
| 10 | int main(void) { |
| 11 | struct Record r1 = { .type = 0, .i = 42 }; |
| 12 | struct Record r2 = { .type = 1, .d = 3.14 }; |
| 13 | |
| 14 | /* Access union members directly — no .value. prefix */ |
| 15 | printf("type=%d val=%d\n", r1.type, r1.i); |
| 16 | printf("type=%d val=%.2f\n", r2.type, r2.d); |
| 17 | return 0; |
| 18 | } |
Bit fields pack integer members into specific numbers of bits. They are useful for hardware registers, protocol headers, and compact flag storage. The layout is implementation-defined:
| 1 | struct Flags { |
| 2 | unsigned int bold : 1; |
| 3 | unsigned int italic : 1; |
| 4 | unsigned int underline : 1; |
| 5 | unsigned int fg_color : 4; /* 0-15 */ |
| 6 | unsigned int bg_color : 4; /* 0-15 */ |
| 7 | }; |
| 8 | |
| 9 | struct IPv4Header { |
| 10 | unsigned int version : 4; |
| 11 | unsigned int ihl : 4; |
| 12 | unsigned int dscp : 6; |
| 13 | unsigned int ecn : 2; |
| 14 | unsigned int length : 16; |
| 15 | }; |
| 16 | |
| 17 | int main(void) { |
| 18 | struct Flags f = { 0 }; |
| 19 | f.bold = 1; |
| 20 | f.fg_color = 12; /* 0-15 */ |
| 21 | f.bg_color = 0; |
| 22 | |
| 23 | printf("sizeof Flags = %zu\n", sizeof(struct Flags)); |
| 24 | printf("bold=%d italic=%d fg=%d bg=%d\n", |
| 25 | f.bold, f.italic, f.fg_color, f.bg_color); |
| 26 | |
| 27 | struct IPv4Header hdr = { .version = 4, .ihl = 5, .length = 60 }; |
| 28 | printf("version=%u ihl=%u length=%u\n", |
| 29 | hdr.version, hdr.ihl, hdr.length); |
| 30 | return 0; |
| 31 | } |
warning
Binary Tree with Self-Referential Structs
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | typedef struct TreeNode { |
| 5 | int data; |
| 6 | struct TreeNode *left; |
| 7 | struct TreeNode *right; |
| 8 | } TreeNode; |
| 9 | |
| 10 | TreeNode *new_node(int data) { |
| 11 | TreeNode *n = malloc(sizeof(TreeNode)); |
| 12 | *n = (TreeNode){ .data = data, .left = NULL, .right = NULL }; |
| 13 | return n; |
| 14 | } |
| 15 | |
| 16 | TreeNode *insert(TreeNode *root, int data) { |
| 17 | if (!root) return new_node(data); |
| 18 | if (data < root->data) |
| 19 | root->left = insert(root->left, data); |
| 20 | else |
| 21 | root->right = insert(root->right, data); |
| 22 | return root; |
| 23 | } |
| 24 | |
| 25 | void inorder(const TreeNode *root) { |
| 26 | if (!root) return; |
| 27 | inorder(root->left); |
| 28 | printf("%d ", root->data); |
| 29 | inorder(root->right); |
| 30 | } |
| 31 | |
| 32 | void free_tree(TreeNode *root) { |
| 33 | if (!root) return; |
| 34 | free_tree(root->left); |
| 35 | free_tree(root->right); |
| 36 | free(root); |
| 37 | } |
| 38 | |
| 39 | int main(void) { |
| 40 | TreeNode *tree = NULL; |
| 41 | int values[] = { 50, 30, 70, 20, 40, 60, 80 }; |
| 42 | int n = sizeof(values) / sizeof(values[0]); |
| 43 | |
| 44 | for (int i = 0; i < n; i++) |
| 45 | tree = insert(tree, values[i]); |
| 46 | |
| 47 | printf("Inorder: "); |
| 48 | inorder(tree); |
| 49 | printf("\n"); |
| 50 | |
| 51 | free_tree(tree); |
| 52 | return 0; |
| 53 | } |
Generic Container with Union
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } DataType; |
| 5 | |
| 6 | typedef struct { |
| 7 | DataType type; |
| 8 | union { |
| 9 | int i; |
| 10 | float f; |
| 11 | char s[64]; |
| 12 | }; |
| 13 | } Cell; |
| 14 | |
| 15 | typedef struct { |
| 16 | Cell cells[16]; |
| 17 | int count; |
| 18 | } Row; |
| 19 | |
| 20 | void row_add_int(Row *r, int val) { |
| 21 | Cell *c = &r->cells[r->count++]; |
| 22 | c->type = TYPE_INT; |
| 23 | c->i = val; |
| 24 | } |
| 25 | |
| 26 | void row_add_float(Row *r, float val) { |
| 27 | Cell *c = &r->cells[r->count++]; |
| 28 | c->type = TYPE_FLOAT; |
| 29 | c->f = val; |
| 30 | } |
| 31 | |
| 32 | void row_add_string(Row *r, const char *val) { |
| 33 | Cell *c = &r->cells[r->count++]; |
| 34 | c->type = TYPE_STRING; |
| 35 | strncpy(c->s, val, 63); |
| 36 | c->s[63] = '\0'; |
| 37 | } |
| 38 | |
| 39 | void print_row(const Row *r) { |
| 40 | for (int i = 0; i < r->count; i++) { |
| 41 | const Cell *c = &r->cells[i]; |
| 42 | switch (c->type) { |
| 43 | case TYPE_INT: printf("%d\t", c->i); break; |
| 44 | case TYPE_FLOAT: printf("%.2f\t", c->f); break; |
| 45 | case TYPE_STRING: printf("%s\t", c->s); break; |
| 46 | } |
| 47 | } |
| 48 | printf("\n"); |
| 49 | } |
| 50 | |
| 51 | int main(void) { |
| 52 | Row row = { .count = 0 }; |
| 53 | row_add_int(&row, 42); |
| 54 | row_add_float(&row, 3.14f); |
| 55 | row_add_string(&row, "hello"); |
| 56 | print_row(&row); /* 42 3.14 hello */ |
| 57 | return 0; |
| 58 | } |