C / STRUCTS, UNIONS AND ENUMS
Defining structs and accessing members
Define a struct type, declare and initialize variables of it, read and write members with the dot operator, and copy a whole struct with plain assignment.
What you will learn
- Write a struct definition and declare variables using the full type name struct T.
- Initialize members positionally with {3, 4} or by name with {.y = 4}.
- Read and assign members with the dot operator, including on array elements.
- Copy a struct with = but compare two structs member by member, never with ==.
Understanding Defining structs and accessing members
A struct definition names a type, not an object. `struct point { int x; int y; };` puts the tag `point` into the tag namespace and describes a layout; it reserves no storage and generates no code. The full type name in C is the two words `struct point`, so a declaration reads `struct point p;`. Member names belong to the struct rather than to the surrounding scope, so another struct type may declare its own `x`, and an ordinary variable called `point` can coexist with the tag `point`.
An object of struct type is one contiguous block of storage holding its members in declaration order. Each member name is an offset the compiler already knows, so `p.y` compiles into "the int at p's address plus the offset of y" — an address computation, not a lookup by name at runtime. That is why the member has to be written literally in the source; there is no way to select a member from a string held in a variable. It also means the dot operator needs a real struct object on its left, and `p.x` is an lvalue you can assign to whenever `p` itself is one.
The same block can be handled as a single value: `struct point q = p;` and a later `q = p;` copy every member, after which `q` still has its own separate storage. A brace initializer may list values positionally, `{3, 4}`, or name them, `{.y = 4}`, and once any initializer is present every member you did not mention is set to zero. Comparison is the exception: `p == q` does not compile, because C defines no equality operator for struct operands, so you write the member tests yourself.
placeholder
<stdio.h>
struct point {
int x;
int y;
};
int main(void)
{
struct point origin = {0, 0};
struct point p = {.x = 3, .y = 4};
struct point q;
q = p; /* copies both members */
q.y = 10; /* q has its own storage, so p is untouched */
printf("origin: (%d, %d)\n", origin.x, origin.y);
printf("p: (%d, %d)\n", p.x, p.y);
printf("q: (%d, %d)\n", q.x, q.y);
printf("p.x + q.y = %d\n", p.x + q.y);
return 0;
}
A struct definition creates a type describing one block of storage, and p.member is a fixed compile-time offset into a specific object of that type rather than a name lookup.
Worked examples
Partial and named initializers
Naming only some members zero-fills the rest, and two structs are compared one member at a time.
<stdio.h>
struct rect {
int w;
int h;
char label[8];
};
int main(void)
{
struct rect a = {5};
struct rect b = {.h = 2, .w = 5};
printf("a: w=%d h=%d label=[%s]\n", a.w, a.h, a.label);
printf("b: w=%d h=%d label=[%s]\n", b.w, b.h, b.label);
printf("widths equal: %d\n", a.w == b.w);
printf("b area: %d\n", b.w * b.h);
return 0;
}
Example explained
Line 1`{5}` sets w only; because an initializer is present, h becomes 0 and label becomes all zero bytes.
Line 2`{.h = 2, .w = 5}` names its members, so the written order is free; storage order still follows the definition.
Line 3`a.label` is a char array whose first byte is 0, so `%s` prints an empty string between the brackets.
Line 4`a.w == b.w` compares two ints and is legal, whereas `a == b` would not compile at all.
An array of structs
Reaches members of individual elements of a struct array inside a loop.
<stdio.h>
struct student {
char name[8];
int score;
};
int main(void)
{
struct student roster[3] = {
{"Ada", 91},
{"Linus", 78},
{"Grace", 95}
};
int total = 0;
int i;
for (i = 0; i < 3; i++) {
printf("%-6s %3d\n", roster[i].name, roster[i].score);
total += roster[i].score;
}
printf("mean %d\n", total / 3);
return 0;
}
Example explained
Line 1`roster[3]` holds three whole struct objects back to back; each inner brace pair initializes one element.
Line 2`roster[i].score` parses as `(roster[i]).score` because subscripting binds tighter than the dot operator.
Line 3`name` is a char[8], so each literal must fit with its terminator; "Grace" uses 6 of the 8 bytes.
Line 4`total / 3` is integer division on 264, which divides exactly, so the mean prints as 88.
Important notes
A local struct declared with no initializer has indeterminate members; `struct point p = {0};` sets all of them to zero.
Designated initializers such as `{.y = 4}` require C99 or later; a pre-C99 compiler accepts only positional lists.
Common mistakes
Forgetting the semicolon after the definition's closing brace: the next function definition is parsed as part of the declaration, so the error message points at a line that looks perfectly fine.
Writing `point p;` instead of `struct point p;`: the tag alone is not a type name in C, so you get "unknown type name 'point'" even though the definition sits right above.
Writing `if (a == b)` for two struct variables: == has no meaning for struct operands and the build fails; you must write `a.x == b.x && a.y == b.y`.
Try it yourself
Change, predict, then run
Define `struct book { char title[32]; int year; double price; };`, then create one book initialized positionally with all three values and a second using only `.year`. Print every member of both, plus a line showing whether the two years are equal.
Open the C workspaceCheck your understanding
Given `struct cfg { int retries; int timeout; };` followed by `struct cfg c = {.timeout = 30};`, what does `printf("%d\n", c.retries);` print, and why?
- 0, because once any initializer is present the members you did not name are set to zero
- An indeterminate value, because retries was never given one
- 30, because members left out repeat the last value in the initializer list
- Nothing, because the declaration fails to compile unless every member is named
Show answer
A brace initializer initializes the whole object, and members you leave out are initialized as if the object had static storage duration, so retries is 0. The indeterminate answer would only apply if the declaration had no initializer at all, as in `struct cfg c;`.