C structures and unions interview questions, with answers
Structures are C's only way to build a data type of your own, and interviewers use them to check that you know how data is really laid out in memory. The questions are about the difference between a struct and a union, why sizeof often exceeds the sum of the members, what -> saves you from, how bit-fields pack flags, and why two structs cannot be compared with ==. Each answer below comes with code whose output was produced by compiling and running it, on a typical 64-bit compiler where an int is 4 bytes.
The questions start with the two kinds of aggregate, move through layout and syntax, and end with the self-referential structs behind every linked list. Then take the free C diagnostic — ten questions across every C topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is the difference between a structure and a union in C?
In short: A struct stores all its members side by side, so it is at least as big as their sum; a union overlaps them in one block the size of its largest member, holding one at a time.
In a struct every member has its own storage at increasing offsets, so all of them hold values at once. In a union every member starts at the same address, so writing one overwrites the others, and the union is only as big as its largest member, rounded up for alignment, as the sizes below show. Unions save memory when an object holds one of several kinds of value at a time, usually inside a struct with a tag field recording which member is live, the tagged union. C also allows reading a member other than the one last written, which reinterprets the stored bytes; that kind of type punning is defined in C, though not in C++, where it is undefined behaviour.
struct s { int i; double d; }; union u { int i; double d; }; printf("%zu %zu\n", sizeof(struct s), sizeof(union u)); // 16 8
2.How can you reduce the padding in a struct, and what does #pragma pack cost?
In short: Ordering members from the largest alignment to the smallest shrinks padding for free; packing removes it entirely but misaligns members, which is slower on some CPUs and can fault on others.
The compiler pads a struct so that each member sits at an offset that is a multiple of its alignment, and pads the end so that arrays of the struct stay aligned. Because the padding depends on member order, declaring members from the largest alignment to the smallest, doubles and pointers first, then ints, then chars, usually removes most of it at no cost: the two structs below hold the same members in 24 and 16 bytes. #pragma pack(1), or gcc's __attribute__((packed)), removes padding entirely, which file formats and network headers sometimes need, but members may then be misaligned: access is slower on x86 and can fault on stricter processors. offsetof from <stddef.h> shows where each member landed.
struct a { char c; double d; char e; }; struct b { double d; char c; char e; }; printf("%zu %zu\n", sizeof(struct a), sizeof(struct b)); // 24 16
3.Why do C programmers typedef their structs?
In short: A struct's name lives in a separate tag namespace in C, so without typedef every use needs the struct keyword; typedef gives the type a one-word name.
Declaring struct item { int id; }; creates the tag item, and in C, unlike C++, a variable must then be declared as struct item x. A typedef, such as typedef struct item Item; or typedef struct { ... } Size; as below, adds an ordinary name for the type, so Size s; works. Style guides disagree about it: the Linux kernel avoids typedef for structs so that the word struct stays visible at every use, while many libraries typedef everything. Two details matter. A struct that points to itself, such as a list node, must use its tag inside its own body, because the typedef name does not exist until the declaration ends. And hiding a pointer behind a typedef usually makes code harder to read.
typedef struct { int w, h; } Size; int area(Size s) { return s.w * s.h; } int main(void) { Size s = {3, 4}; printf("%d\n", area(s)); } // 12
4.What does the -> operator do in C, and why is *p.x wrong?
In short: p->x is shorthand for (*p).x, reaching a member through a pointer; *p.x fails because . binds tighter than *, so it means *(p.x).
Member access with . needs a struct value, and following a pointer needs *, but the postfix . operator binds more tightly than unary *, so *p.x parses as *(p.x), which does not compile when p is a pointer. The correct long form is (*p).x, and -> exists to make that common operation readable: p->x means exactly (*p).x, and both lines in the function below change the same member. It chains naturally through linked structures, as in node->next->value. Passing a pointer to a struct and using -> inside is the usual way to let a function modify the caller's struct without copying it, and a pointer to const promises the function will only read it.
struct acct { int bal; }; void pay(struct acct *a) { a->bal -= 12; (*a).bal -= 8; } int main(void) { struct acct x = {90}; pay(&x); printf("%d\n", x.bal); } // 70
5.What are bit-fields in C, and what can't you do with them?
In short: Bit-fields let struct members occupy a stated number of bits, packing flags tightly, but you cannot take their address, and their layout is up to the compiler.
A member declared unsigned on : 1; occupies a single bit, and several such members can share one storage unit, so a set of flags or small counters fits in a few bytes. Assigning a value that does not fit keeps only the low bits of an unsigned field, as the 3-bit field below shows: 9 is 1001 in binary, and only 001 remains, which gcc warns about. Their limits come up in interviews: you cannot take the address of a bit-field or point to one, and the order of fields within a unit, whether a field may straddle two units, and even the signedness of a plain int bit-field are implementation-defined. So bit-fields must not be used to match an external layout such as a hardware register or a network header; use masks and shifts for that.
struct flags { unsigned small : 3; unsigned on : 1; }; struct flags f = {0, 1}; f.small = 9; printf("%u %u\n", f.small, f.on); // 1 1
6.Can you compare two structs with == in C?
In short: No: == is not defined for structs, so compare them member by member, and avoid memcmp, because padding bytes may hold garbage.
C allows assigning, passing and returning whole structs, but not comparing them: p == q between two structs is a compile error. The tempting shortcut, memcmp(&p, &q, sizeof p), compares every byte including the padding, whose contents are unspecified, so two structs with equal members can compare unequal. It also compares pointer members by address rather than by what they point to, and floating-point members bit by bit, which gets -0.0 and NaN wrong. The reliable way is a small function that compares the members that define equality, as below. C++ lets a class define operator== for this, and C++20 can even generate it with = default.
struct v2 { int x, y; }; int eq(const struct v2 *a, const struct v2 *b) { return a->x == b->x && a->y == b->y; } int main(void) { struct v2 p = {1, 2}; struct v2 q = {1, 2}; if (p == q) {} // does not compile printf("%d\n", eq(&p, &q)); } // 1
7.How do you declare a linked-list node in C, and why can't a struct contain itself?
In short: Give the node a pointer to its own struct type, struct node *next; a member of the struct's own type would make it infinitely large, but a pointer has a fixed size.
A struct's size must be known when its definition ends, so it cannot contain a member of its own type: that member would contain another, and so on without end. A pointer to the struct is fine, because every pointer has a fixed size whether or not the struct it points to is complete, which is why struct node { int v; struct node *next; }; works and is the basis of every linked list, tree and graph in C. The same rule lets two structs point to each other after a forward declaration such as struct b;. Nodes are usually allocated with malloc so they outlive the function that creates them, and each must later be freed, which the loop below does while printing.
struct node { int v; struct node *next; }; int main(void) { struct node *head = NULL; int v = 1; while (v <= 3) { struct node *n = malloc(sizeof *n); n->v = v++; n->next = head; head = n; } while (head) { struct node *t = head; head = t->next; printf("%d ", t->v); free(t); } } // 3 2 1
How the diagnostic asks it
One question from the C bank, exactly as a sitting would show it. The bank has 3 on structures & unions and 30 across C.
On a typical compiler, where an int is 4 bytes and aligned to 4, what does this C code print?
struct s { char a; int b; char c; }; printf("%zu\n", sizeof(struct s));
- 112correct
- 26
- 38
- 49
The compiler inserts padding so every member is correctly aligned. a takes offset 0, then 3 bytes of padding let b start at offset 4; b fills offsets 4 to 7, and c sits at offset 8. Then 3 more bytes of tail padding round the size up to 12, a multiple of the struct's alignment, 4, so that b stays aligned in every element of an array. 6 is the sum of the members without any padding. 9 counts the padding before b but stops right after c, missing the tail padding. 8 assumes the compiler moves c up next to a; C keeps members in the order they are declared, so that saving comes only from reordering them yourself: int b; char a; char c; gives 8.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 C questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.