Unions and Enumerations

A union is like a multi-purpose charging cable — you can only use one interface at a time, but it saves space. An enum is like a menu number — using names instead of raw numbers makes code more readable.

union Definition and Shared Memory

All members of a union share the same memory. Its size equals the size of its largest member. Only one member can be used at a time.

C
union Data {
    int i;
    float f;
    char str[4];
};

printf("%zu\n", sizeof(union Data));
TEXT
4

After assigning to the int member, reading the float member gives a different interpretation of the same memory:

C
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
TEXT
i = 42
f = 0.000000
⚠️ Note: Reading a member other than the one last written is implementation-defined (depends on platform and types). In practice, use a tag field to distinguish which member is currently valid.

union vs. struct

Feature struct union
Memory Each member has its own space All members share the same space
Size Sum of all member sizes (with alignment padding) Size of the largest member
Simultaneous access All members can be accessed at the same time Only one member at a time
Modification Changing one doesn't affect others Changing one affects all
C
struct SData {
    int i;
    float f;
    char str[4];
};

union UData {
    int i;
    float f;
    char str[4];
};

printf("struct: %zu\n", sizeof(struct SData));
printf("union:  %zu\n", sizeof(union UData));
TEXT
struct: 12
union:  4

Tagged Unions

In practice, unions are typically paired with a tag field that indicates which type is currently stored:

C
typedef struct {
    int type;
    union {
        int int_val;
        float float_val;
        char str_val[32];
    } value;
} Variant;

void print_variant(const Variant *v) {
    switch (v->type) {
        case 0: printf("int: %d\n", v->value.int_val); break;
        case 1: printf("float: %f\n", v->value.float_val); break;
        case 2: printf("str: %s\n", v->value.str_val); break;
    }
}

Example

C
#include <stdio.h>

typedef struct {
    int kind;
    union {
        int ival;
        double dval;
    } u;
} Number;

void print_number(const Number *n) {
    if (n->kind == 0) {
        printf("int: %d\n", n->u.ival);
    } else {
        printf("double: %.2f\n", n->u.dval);
    }
}

int main(void) {
    Number a = {0, .u.ival = 42};
    Number b = {1, .u.dval = 3.14};
    print_number(&a);
    print_number(&b);
    return 0;
}
▶ Try it Yourself
TEXT
int: 42
double: 3.14

enum Definition and Usage

Enumerations use names instead of integer constants, improving code readability.

C
enum Color {
    RED,
    GREEN,
    BLUE
};

enum Color favorite = GREEN;
if (favorite == GREEN) {
    printf("Green selected\n");
}
TEXT
Green selected

Specifying Enum Values

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
TEXT
MON=1 SAT=100 SUN=101
💡 Tip: Enum constants without an explicit value automatically take the previous value + 1. The first defaults to 0.

enum with switch

C
enum Direction { UP, DOWN, LEFT, RIGHT };

void move(enum Direction d) {
    switch (d) {
        case UP:    printf("Move up\n"); break;
        case DOWN:  printf("Move down\n"); break;
        case LEFT:  printf("Move left\n"); break;
        case RIGHT: printf("Move right\n"); break;
    }
}
⚠️ Note: Enums are backed by int — you can assign any integer value, and the compiler may not warn. Don't abuse this feature.

Underlying int Values of Enums

Enum constants are essentially named constants of type int.

C
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
TEXT
200 404 500

Enum variables can be assigned to and from integers (in C), but it's best to use enum values for type consistency:

C
enum Status s = OK;
int code = s;
💡 Tip: Prefer enums over #define for constants: enums have type information, display symbolic names in debuggers, and have more controllable scope.

Using union to Detect Endianness

Endianness refers to the byte order of multi-byte data in memory. A union makes it easy to detect:

C
int is_little_endian(void) {
    union {
        int i;
        char c;
    } u;
    u.i = 1;
    return u.c == 1;
}

If the lowest byte is stored at the lowest address (u.c == 1), it's little-endian; otherwise it's big-endian.

Example

C
#include <stdio.h>

typedef union {
    unsigned int i;
    unsigned char bytes[4];
} EndianTest;

void print_byte_order(void) {
    EndianTest t;
    t.i = 0x12345678;
    printf("Memory byte order: ");
    for (int i = 0; i < 4; i++) {
        printf("%02X ", t.bytes[i]);
    }
    printf("\n");
    if (t.bytes[0] == 0x78) {
        printf("Little Endian\n");
    } else {
        printf("Big Endian\n");
    }
}

int main(void) {
    print_byte_order();
    return 0;
}
▶ Try it Yourself
TEXT
Memory byte order: 78 56 34 12
Little Endian
🔥 Common Mistake: x86/ARM are typically little-endian, while network transmission uses big-endian (network byte order). Understanding endianness is essential for protocol parsing and file format handling.

union for Memory Savings in Practice

When a struct has multiple mutually exclusive fields, use a union to save space:

C
typedef struct {
    int type;
    union {
        struct { int width, height; } rect;
        struct { int radius; } circle;
        struct { int side; } square;
    } shape;
} Figure;

Figure f;
f.type = 1;
f.shape.circle.radius = 10;

Only one of the three shapes is used at a time. The union shares memory, and the struct size is determined by the largest shape.

❓ FAQ

Q Can a union use multiple members at the same time?
A No. Union members share memory — writing one overwrites the others. You should only read the member that was last written.
Q Can I print an enum name with %s?
A No. Enum values are integers. To print names, you need to write a mapping function or array, e.g. const char *names[] = {"RED","GREEN","BLUE"}.
Q What's the difference between enum and #define for constants?
A enum has a type (int), debuggers can display symbolic names, and scope is more controlled. #define is a preprocessor substitution with no type information. Prefer enum.
Q Why is using a union to detect endianness better than pointer casting?
A They're essentially the same, but the union approach is more in line with C's type-safety spirit, avoids strict aliasing rule issues, and is more readable.

📖 Summary

📝 Exercises

  1. Define union IPAddress containing unsigned int addr and unsigned char bytes[4]. Input an IP integer value and output it as both an integer and four separate bytes
  2. Define an enum enum Season { SPRING, SUMMER, AUTUMN, WINTER } and write a function that returns the corresponding season name string based on the enum value
  3. Use a tagged union to implement a simple shape struct with a type tag and union (rectangle/circle), then write a function to calculate the area
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏