Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Structs, Unions & Undefined Behavior
C

Structs, Unions & Undefined Behavior

Practice 10 challenging questions covering structures, unions, memory layout, undefined behavior, and dangerous C programming patterns.

1. What will be the output of the following integer evaluation due to Sequence Point / Evaluation rules?

int i = 5;
int val = i++ + ++i;

Output: Undefined behavior (the program has no defined result).

The expression i++ + ++i modifies i twice in the same full expression, with no sequence point ordering the two modifications. The C standard says: if a scalar is modified more than once (or modified and read) between two sequence points, the behavior is undefined. So:

  • The result isn’t “12” or “13” — it could be anything.
  • The compiler may evaluate in any order; there’s no guaranteed value.

This is the classic C trap: don’t use a variable more than once in an expression where it’s being modified. The interview answer: undefined behaviori is modified twice without an intervening sequence point.

Answer:

Undefined behavior (the program has no defined result).

The expression i++ + ++i modifies i twice in the same full expression, with no sequence point ordering the two modifications. The C standard says: if a scalar is modified more than once (or modified and read) between two sequence points, the behavior is undefined. So:

  • The result isn’t “12” or “13” — it could be anything.
  • The compiler may evaluate in any order; there’s no guaranteed value.

This is the classic C trap: don’t use a variable more than once in an expression where it’s being modified. The interview answer: undefined behaviori is modified twice without an intervening sequence point.

2. What structural padding issue arises in this struct on 64-bit systems?

struct Data {
    char a;
    double b;
    int c;
};

Answer: Padding bytes are inserted after a (7 bytes) and after c (4 bytes), making the struct 24 bytes with 8-byte alignment.

Members must sit at natural alignment boundaries — a double needs 8-byte alignment:

  • char a at offset 0 (1 byte), then 7 padding bytes.
  • double b at offset 8 (8 bytes, ends at 16).
  • int c at offset 16 (4 bytes, ends at 20).
  • The struct’s total size must be a multiple of its alignment (8, the strictest member), so 4 trailing padding bytes → 24 bytes total.

So sizeof(struct Data) is 24, not 1 + 8 + 4 = 13. The compiler never reorders members (order is guaranteed by the standard); it only inserts padding. Reordering members manually (biggest first) is the classic way to shrink struct sizes. The interview answer: 7 padding after a, 4 after c, struct size 24 with 8-byte alignment.

Answer:

Padding bytes are inserted after a (7 bytes) and after c (4 bytes), making the struct 24 bytes with 8-byte alignment.

Members must sit at natural alignment boundaries — a double needs 8-byte alignment:

  • char a at offset 0 (1 byte), then 7 padding bytes.
  • double b at offset 8 (8 bytes, ends at 16).
  • int c at offset 16 (4 bytes, ends at 20).
  • The struct’s total size must be a multiple of its alignment (8, the strictest member), so 4 trailing padding bytes → 24 bytes total.

So sizeof(struct Data) is 24, not 1 + 8 + 4 = 13. The compiler never reorders members (order is guaranteed by the standard); it only inserts padding. Reordering members manually (biggest first) is the classic way to shrink struct sizes. The interview answer: 7 padding after a, 4 after c, struct size 24 with 8-byte alignment.

3. What does typedef do in C?

Answer: It creates an alias (new name) for an existing type.

typedef doesn’t create a new kind of type — it introduces a synonym:

typedef unsigned long ulong;
typedef struct { int x, y; } Point;

ulong big = 42;      // unsigned long
Point p = {1, 2};    // the struct

Uses:

  • Readability — names like Point instead of struct {...}.
  • Portability — abstract platform-specific types (size_t, uint32_t) behind one name.
  • Reducing verbositytypedef struct Node { ... } Node; lets you write Node instead of struct Node in C.

Note: typedef names are part of the ordinary identifier namespace (unlike struct tags), so they follow normal scope rules. The interview answer: typedef defines an alias for an existing type.

Answer:

It creates an alias (new name) for an existing type.

typedef doesn’t create a new kind of type — it introduces a synonym:

typedef unsigned long ulong;
typedef struct { int x, y; } Point;

ulong big = 42;      // unsigned long
Point p = {1, 2};    // the struct

Uses:

  • Readability — names like Point instead of struct {...}.
  • Portability — abstract platform-specific types (size_t, uint32_t) behind one name.
  • Reducing verbositytypedef struct Node { ... } Node; lets you write Node instead of struct Node in C.

Note: typedef names are part of the ordinary identifier namespace (unlike struct tags), so they follow normal scope rules. The interview answer: typedef defines an alias for an existing type.

4. What does the enum feature in C construct?

Answer: A user-defined enumeration type: named integer constants that improve readability.

enum creates a set of named constants:

enum Color { RED, GREEN, BLUE };
enum Color c = GREEN;   // c == 1

By default the constants start at 0 and increment by 1 (RED=0, GREEN=1, BLUE=2), but you can assign explicit values:

enum Status { OK = 0, ERROR = -1, TIMEOUT = 2 };

Benefits: self-documenting code (names instead of magic numbers), and the compiler can warn about unhandled cases. In C, enum values are just ints underneath — they interconvert freely with integers (unlike C++ where the rules are stricter). The interview answer: an enumeration of named integer constants for readability and type-organization.

Answer:

A user-defined enumeration type: named integer constants that improve readability.

enum creates a set of named constants:

enum Color { RED, GREEN, BLUE };
enum Color c = GREEN;   // c == 1

By default the constants start at 0 and increment by 1 (RED=0, GREEN=1, BLUE=2), but you can assign explicit values:

enum Status { OK = 0, ERROR = -1, TIMEOUT = 2 };

Benefits: self-documenting code (names instead of magic numbers), and the compiler can warn about unhandled cases. In C, enum values are just ints underneath — they interconvert freely with integers (unlike C++ where the rules are stricter). The interview answer: an enumeration of named integer constants for readability and type-organization.

5. What does the union structure do in C?

Answer: All members share the same memory location, sized to fit the largest member — only one member is meaningful at a time.

A union overlays all its members at the same starting address:

union Value {
    int i;
    float f;
    char bytes[4];
};
// sizeof(union Value) == 4 (largest member)

Writing to one member overwrites the shared storage — reading a different member reinterprets those same bytes. The union is big enough for its largest member (plus alignment padding).

Use cases:

  • Type punning — inspect the bytes of a float as an int (with care; strict aliasing rules apply, and C23’s typeof punning via unions became legal).
  • Memory savings — when a variable is one-of-several types, a union uses max-size instead of the sum.
  • Variant/tagged structures — a union plus a discriminant enum field telling which member is active.

The responsibility: track which member is currently valid — there’s no automatic check. The interview answer: a single shared memory region sized for the largest member; only one field can safely hold a value at a time.

Answer:

All members share the same memory location, sized to fit the largest member — only one member is meaningful at a time.

A union overlays all its members at the same starting address:

union Value {
    int i;
    float f;
    char bytes[4];
};
// sizeof(union Value) == 4 (largest member)

Writing to one member overwrites the shared storage — reading a different member reinterprets those same bytes. The union is big enough for its largest member (plus alignment padding).

Use cases:

  • Type punning — inspect the bytes of a float as an int (with care; strict aliasing rules apply, and C23’s typeof punning via unions became legal).
  • Memory savings — when a variable is one-of-several types, a union uses max-size instead of the sum.
  • Variant/tagged structures — a union plus a discriminant enum field telling which member is active.

The responsibility: track which member is currently valid — there’s no automatic check. The interview answer: a single shared memory region sized for the largest member; only one field can safely hold a value at a time.

6. What is the behavior of reading a union member different from the one most recently written to?

Answer: It reinterprets the raw bit pattern as the new member’s type — type-punning, which C explicitly permits.

Unlike C++, C allows reading a union member other than the one last written — a feature called type punning. The stored bytes are reinterpreted as the target member’s type. This is only meaningful when the members are the same size (and alignment-compatible):

union { float f; uint32_t i; } u;
u.f = 1.5f;
printf("%08x", u.i);   // raw IEEE-754 bits of 1.5f, as an unsigned int

This is the classic low-level idiom for inspecting the binary representation of a value (e.g., floating-point bit manipulation) without memcpy or pointer-cast aliasing games. (C23 formalized and strengthened these guarantees.) Caveat: the interpretation depends on the representation — it’s a deliberate bit-level view, not a “safe conversion.” The interview answer: the stored bit pattern is read as the new member’s type (type-punning), supported when sizes/alignment match.

Answer:

It reinterprets the raw bit pattern as the new member’s type — type-punning, which C explicitly permits.

Unlike C++, C allows reading a union member other than the one last written — a feature called type punning. The stored bytes are reinterpreted as the target member’s type. This is only meaningful when the members are the same size (and alignment-compatible):

union { float f; uint32_t i; } u;
u.f = 1.5f;
printf("%08x", u.i);   // raw IEEE-754 bits of 1.5f, as an unsigned int

This is the classic low-level idiom for inspecting the binary representation of a value (e.g., floating-point bit manipulation) without memcpy or pointer-cast aliasing games. (C23 formalized and strengthened these guarantees.) Caveat: the interpretation depends on the representation — it’s a deliberate bit-level view, not a “safe conversion.” The interview answer: the stored bit pattern is read as the new member’s type (type-punning), supported when sizes/alignment match.

7. What is a Flexible Array Member in C structs introduced in C99?

Answer: An unsized array as the last member of a struct (e.g., int data[];) that lets you allocate a variable-length payload contiguous with the struct header.

A flexible array member is declared as the last struct member without a size:

struct Packet {
    int length;
    char data[];   // flexible array member
};

You allocate the struct and its payload in one contiguous block:

struct Packet *p = malloc(sizeof(struct Packet) + payload_bytes);
p->length = payload_bytes;
p->data[0] = ...;   // reach the inline payload

Rules: it must be the last member; there must be at least one other named member; the struct is sized as if the array were absent (with trailing padding for alignment). This is the standard way to build header+payload messages with a single allocation and good locality. The interview answer: a trailing unsized array (data[]) enabling contiguous struct-header + variable-payload allocation.

Answer:

An unsized array as the last member of a struct (e.g., int data[];) that lets you allocate a variable-length payload contiguous with the struct header.

A flexible array member is declared as the last struct member without a size:

struct Packet {
    int length;
    char data[];   // flexible array member
};

You allocate the struct and its payload in one contiguous block:

struct Packet *p = malloc(sizeof(struct Packet) + payload_bytes);
p->length = payload_bytes;
p->data[0] = ...;   // reach the inline payload

Rules: it must be the last member; there must be at least one other named member; the struct is sized as if the array were absent (with trailing padding for alignment). This is the standard way to build header+payload messages with a single allocation and good locality. The interview answer: a trailing unsized array (data[]) enabling contiguous struct-header + variable-payload allocation.

8. What is the difference between structure field access operators . and ->?

Answer: . accesses a member directly on a struct instance; -> accesses a member through a pointer to a struct — and ptr->member is shorthand for (*ptr).member.

struct Point p;         // instance
p.x = 5;                // . on the instance

struct Point *pp = &p;
pp->x = 7;              // -> dereferences the pointer
(*pp).x = 7;            // equivalent, more verbose

So . needs the struct itself (or a reference to it); -> dereferences the pointer first, then accesses the member. Using . where you have a pointer (or -> where you have an instance) is a compile error. The arrow is just the idiomatic, more readable version of (*ptr).member. The interview answer: . on instances, -> on pointers (dereference + access).

Answer:

. accesses a member directly on a struct instance; -> accesses a member through a pointer to a struct — and ptr->member is shorthand for (*ptr).member.

struct Point p;         // instance
p.x = 5;                // . on the instance

struct Point *pp = &p;
pp->x = 7;              // -> dereferences the pointer
(*pp).x = 7;            // equivalent, more verbose

So . needs the struct itself (or a reference to it); -> dereferences the pointer first, then accesses the member. Using . where you have a pointer (or -> where you have an instance) is a compile error. The arrow is just the idiomatic, more readable version of (*ptr).member. The interview answer: . on instances, -> on pointers (dereference + access).

9. What is the purpose of alignment macro alignof / _Alignof introduced in C11?

Answer: It queries the alignment requirement (in bytes) of a specified type.

alignof(type) (or its spelling _Alignof; <stdalign.h> provides the alignof macro) returns the byte alignment that objects of that type must have:

alignof(char)    // typically 1
alignof(int)     // typically 4
alignof(double)  // typically 8

This is the natural alignment the compiler enforces when placing the type in memory (e.g., a double at offset multiple of 8). C11 also added _Alignas (declaration specifier to request alignment) and aligned_alloc (allocate with a given alignment). These matter for hardware, vector types, and ABI-compatible struct layouts. The interview answer: alignof(type) returns the type’s required byte alignment.

Answer:

It queries the alignment requirement (in bytes) of a specified type.

alignof(type) (or its spelling _Alignof; <stdalign.h> provides the alignof macro) returns the byte alignment that objects of that type must have:

alignof(char)    // typically 1
alignof(int)     // typically 4
alignof(double)  // typically 8

This is the natural alignment the compiler enforces when placing the type in memory (e.g., a double at offset multiple of 8). C11 also added _Alignas (declaration specifier to request alignment) and aligned_alloc (allocate with a given alignment). These matter for hardware, vector types, and ABI-compatible struct layouts. The interview answer: alignof(type) returns the type’s required byte alignment.

10. What does offsetof(type, member) in <stddef.h> calculate?

Answer: The byte offset of a structure member from the start of the struct, accounting for padding.

offsetof(struct_type, member) returns the number of bytes from the beginning of the struct to that member, including any padding the compiler inserted for alignment:

struct Data { char a; double b; int c; };
offsetof(struct Data, b)   // 8 (7 padding bytes after a)
offsetof(struct Data, c)   // 16

Uses: manual serialization, building generic reflection/field tables, allocating flexible layouts. It’s a compile-time constant (works in static contexts and constant expressions). The interview answer: the padded byte offset of a member within its struct.

Answer:

The byte offset of a structure member from the start of the struct, accounting for padding.

offsetof(struct_type, member) returns the number of bytes from the beginning of the struct to that member, including any padding the compiler inserted for alignment:

struct Data { char a; double b; int c; };
offsetof(struct Data, b)   // 8 (7 padding bytes after a)
offsetof(struct Data, c)   // 16

Uses: manual serialization, building generic reflection/field tables, allocating flexible layouts. It’s a compile-time constant (works in static contexts and constant expressions). The interview answer: the padded byte offset of a member within its struct.

My Private Notes

Notes are auto-saved locally to this device.