A struct, short for structure, is the way C groups values into one object. The C standard describes a structure type as a sequentially allocated, nonempty set of member objects. Each member has a name, and the members can have different types. A dot reaches a member of an object, an arrow reaches a member through a pointer, and typedef can give the type a shorter name.
Two things deserve attention beyond the syntax. Members are stored in the order of declaration, but the implementation may insert padding between them and after the last one, so the size of a struct is at least the sum of its members and often more. And the order of the members can change that size. The numbers on this page come from programs built with cc -std=c11 -Wall -Wextra -pedantic on Apple clang 12.0.0, on arm64 and on x86-64 with the LP64 data model.
Struct definition: what a struct is in C
A struct is a record. It exists so that values which belong together travel together: the two coordinates of a point, the fields of a customer, the value and the next pointer of a list node. A member can be a number, a pointer, an array or another struct. It cannot be a function, because the standard does not allow a member of function type, and it cannot be an incomplete type, except that a struct may hold a pointer to its own type.
A union looks the same on the page but differs in storage. The standard describes a structure as a sequentially allocated set of members and a union as an overlapping set. In a struct every member has its own storage and all of them exist at once. In a union the members share storage, and only one holds a value at a time.
Structs also shape interfaces. A function that takes one struct point has a single parameter instead of two, and the type name documents what the values mean. Returning a struct hands back several values at once without out parameters.
The struct is also the basis of larger patterns. A struct together with functions that take a pointer to it forms the usual building block of classes in C, and hiding the definition behind a header turns it into an opaque type.
Struct syntax in C: declaring, creating and accessing
A declaration names the type with a tag and lists the members. A second struct can use the first as a member. Variables are created with the word struct and the tag, optionally with a brace-enclosed initializer.
#include <stdio.h>
struct point {
double x;
double y;
};
struct segment {
struct point from;
struct point to;
};
int main(void) {
struct point p = { 1.0, 2.0 };
struct point *q = &p;
struct segment s = { { 0.0, 0.0 }, { 3.0, 4.0 } };
q->x = 5.0;
printf("%.1f %.1f\n", p.x, q->y);
printf("%.1f\n", s.to.y);
return 0;
} The program prints 5.0 2.0 and then 4.0. The dot works on the object p and on the nested member s.to. The arrow works on the pointer q, and q->x = 5.0 changes the original p, because the pointer holds its address. For a pointer q, q->x means the same as (*q).x.
A declaration alone reserves no storage. struct point { double x; double y; }; introduces the type, and memory appears when a variable is declared. Variables can also follow the closing brace, as in struct point { double x, y; } a, b;, though separate declarations read better.
The tag point lives in a name space of its own, apart from variable names. A variable can therefore be called point next to a type with the tag point, although that style invites confusion.
How is a struct laid out in memory?
The addresses of the members rise in the order of declaration. A pointer to a struct, suitably converted, points to its first member, and the standard allows no padding at the start. Everywhere else padding is possible: between members, so that each one meets its alignment requirement, and after the last member, so that an array of such structs keeps every element aligned. How alignment works is up to the implementation, which is why the numbers below belong to a platform.
#include <stdio.h>
#include <stddef.h>
struct a { char c; double d; int i; };
struct b { double d; int i; char c; };
struct c3 { char c; int i; double d; };
int main(void) {
printf("a: c=%zu d=%zu i=%zu size=%zu\n", offsetof(struct a, c), offsetof(struct a, d), offsetof(struct a, i), sizeof(struct a));
printf("b: d=%zu i=%zu c=%zu size=%zu\n", offsetof(struct b, d), offsetof(struct b, i), offsetof(struct b, c), sizeof(struct b));
printf("c3: size=%zu\n", sizeof(struct c3));
printf("sum of member sizes in a: %zu\n", sizeof(char) + sizeof(double) + sizeof(int));
return 0;
} a: c=0 d=8 i=16 size=24
b: d=0 i=8 c=12 size=16
c3: size=16
sum of member sizes in a: 13 These values came out the same on arm64 and on x86-64 with the LP64 data model, where double has size 8 and alignment 8. For struct a the layout is:
| Member | Offset | Size |
|---|---|---|
char c | 0 | 1 |
| padding | 1 | 7 |
double d | 8 | 8 |
int i | 16 | 4 |
| padding | 20 | 4 |
| Total | 24 |
The members add up to 13 bytes and the struct takes 24. Putting the large members first, as in struct b, places d at 0, i at 8 and c at 12 and leaves 3 bytes of padding at the end, for a size of 16. Reordering does not always help: struct c3, declared as char, int, double, already takes 16 bytes, and moving its members changes nothing. Measuring with sizeof and offsetof on the target platform is the only dependable way to know. The macro offsetof comes from stddef.h and takes the struct type and the member name, as the program above shows. A layout that matters for a file format or a network message therefore needs explicit handling and cannot be assumed from the declaration.
Initializing a struct in C: lists, designators and compound literals
The first initializer in a braced list belongs to the first member, the second to the second, and so on. Since C99, a designator such as .z = 3.0 names the member instead, and the order of designators is free. Members that receive no initializer are default-initialized, which gives zero for numbers and null for pointers. A compound literal, written as a type in parentheses followed by a braced list, creates an unnamed object with the same rules.
#include <stdio.h>
struct vec {
double x, y, z;
};
static void show(const char *label, struct vec v) {
printf("%-10s %.1f %.1f %.1f\n", label, v.x, v.y, v.z);
}
int main(void) {
struct vec a = { 1.0, 2.0, 3.0 };
struct vec b = { .z = 3.0, .x = 1.0 };
struct vec c = { 0 };
struct vec d = { .x = 1.0, .y = 2.0 };
struct vec e;
e = (struct vec){ .y = 9.0 };
show("list", a);
show("designated", b);
show("zero", c);
show("partial", d);
show("literal", e);
return 0;
} list 1.0 2.0 3.0
designated 1.0 0.0 3.0
zero 0.0 0.0 0.0
partial 1.0 2.0 0.0
literal 0.0 9.0 0.0 | Form | Example | Members not named |
|---|---|---|
| Positional list | { 1.0, 2.0, 3.0 } | Default-initialized when the list is shorter |
| Designators | { .z = 3.0, .x = 1.0 } | Default-initialized |
| Zero | { 0 } | All members cleared |
| Compound literal | (struct vec){ .y = 9.0 } | Default-initialized |
A shorter positional list such as { 1.0, 2.0 } is valid, but clang 12 warns about it under -Wextra because a member was left out. Designators or { 0 } say the same thing without the warning. A struct declared inside a function without any initializer holds unspecified values, so reading it before writing it is a mistake. Compound literals also work as arguments: show((struct vec){ .y = 9.0 }) passes a struct that was never given a name.
Should I use typedef for structs?
A typedef declares an alias for a type and creates no new type. It matters for structs because a tag lives in its own name space, so every use of the plain tag needs the word struct in front of it, while a typedef name is an ordinary identifier. There are three common forms.
| Form | Spelling | When it fits |
|---|---|---|
| Tag only | struct point1 { int x, y; }; | A type used in few places, or code that prefers the word struct at each use |
| Typedef with tag | typedef struct point2 { int x, y; } point2; | Types in headers, and any struct that refers to itself |
| Anonymous typedef | typedef struct { int x, y; } point3; | Simple types that never refer to themselves |
#include <stdio.h>
struct point1 { int x, y; };
typedef struct point2 { int x, y; } point2;
typedef struct { int x, y; } point3;
typedef struct node {
int value;
struct node *next;
} node;
int main(void) {
struct point1 a = { 1, 2 };
point2 b = { 3, 4 };
point3 c = { 5, 6 };
node n2 = { 8, NULL };
node n1 = { 7, &n2 };
printf("%d %d %d %d\n", a.x, b.x, c.x, n1.next->value);
return 0;
} The program prints 1 3 5 8. The list node shows the one hard rule: node contains a pointer to struct node, and inside the declaration the alias does not exist yet, so the tag is needed. Two structs that point at each other need a forward declaration: struct b2; creates an incomplete type, which is enough for a pointer, and the full definition can follow. Whether to use a typedef at all is a question of style, and the standard has no opinion. Editorial recommendation: give types that appear in headers a typedef with the same name as the tag, and leave small local structs without one.
How do arrays and structs work together?
Both directions are common. An array of structs is a table of records, indexed in the usual way. A struct can also contain an array of fixed length as a member. The third form is the flexible array member, introduced in C99: the last member is declared as an array without a length, sizeof leaves it out, and the programmer allocates room for the extra elements behind the struct. Such a struct cannot be an element of an array or a member of another struct.
#include <stdio.h>
#include <stdlib.h>
struct item { const char *name; int qty; };
struct bag { int ids[3]; };
struct buf { size_t n; double data[]; };
int main(void) {
struct item shelf[2] = { { "bolt", 40 }, { "nut", 25 } };
struct bag g = { { 7, 8, 9 } };
struct buf *b = malloc(sizeof *b + 4 * sizeof b->data[0]);
if (b == NULL) return 1;
b->n = 4;
for (size_t i = 0; i < b->n; i++) b->data[i] = (double)i;
printf("%s %d\n", shelf[1].name, shelf[1].qty);
printf("%d\n", g.ids[2]);
printf("%zu %.1f %zu\n", b->n, b->data[3], sizeof *b);
free(b);
return 0;
} The program prints nut 25, then 9, then 4 3.0 8. The last number is the size of the struct itself, 8 bytes on this platform, which does not include the four doubles allocated after it. The call malloc(...) assigned to a typed pointer is valid C, but it is rejected by a C++ compiler without a cast; the comparison of C vs C++ explains why.
Passing structs to functions and choosing something else
Passing a struct by value copies all of it. Assignment between structs copies the members too, though padding bytes take unspecified values in the copy, so comparing two structs with memcmp is unreliable and a member-by-member comparison is safe. For anything larger than a few members, a pointer is the usual way to pass a struct. A pointer to const marks a function that only reads, and the compiler rejects an accidental write through it. Passing by value still has its place for small structs such as a point, where the copy costs little and the function gets a private working copy to change.
#include <stdio.h>
struct big { double samples[64]; int count; };
static double mean(const struct big *b) {
double sum = 0.0;
for (int i = 0; i < b->count; i++) sum += b->samples[i];
return b->count ? sum / b->count : 0.0;
}
static struct big with_one_more(struct big b, double v) {
b.samples[b.count++] = v;
return b;
}
int main(void) {
struct big b = { .count = 0 };
b = with_one_more(b, 2.0);
b = with_one_more(b, 4.0);
printf("%.1f %zu\n", mean(&b), sizeof b);
return 0;
} The program prints 3.0 520: the mean of the two samples and the size of the struct in bytes. mean receives an address, while with_one_more copies all 520 bytes in and again out, which is fine for a demonstration and wasteful in a loop. The copy is also the reason the original b in main only changes because the result is assigned back to it.
Some jobs fit other types better. Values of one type reached by index belong in an array. A single value that may take one of several shapes belongs in a union. A type whose internals should stay private is better served by an opaque struct behind functions, the pattern behind classes in C.
Frequently asked questions
01Should I use typedef for structs?
The standard does not take a side, so this is an editorial recommendation. A typedef with the same name as the tag suits types used across several files, because callers write the short name. A struct used in one function needs no alias. The one case that always needs a tag is a struct that points to its own type.
02How do you initialize a struct in C?
Put the starting values in braces, either in member order or with designators such as .x = 1.0. Members that are left out are default-initialized, which gives zero for numbers and null for pointers. A braced 0 clears the whole struct.
03When not to use struct?
A struct is the wrong tool when all values have the same type and are reached by index, which is an array, or when only one of several alternatives is ever stored, which is a union. It is also a poor fit for a huge value passed around by copy, where a pointer does the job.
04What is the syntax for structs in C?
The word struct is followed by an optional tag and a brace-enclosed list of member declarations, as in struct point { double x; double y; };. A variable is declared with struct point p;, members are reached with p.x, and through a pointer with q->x.
05What is a struct in C programming?
It is a type that groups named members of possibly different types into one object. The members sit in memory in the order they are declared, with padding where the platform needs it. Records, points and list nodes are typical uses.
06Can a struct contain an array in C?
Yes. A member can be an array of fixed length, as in int ids[3];. The last member may also be a flexible array member declared without a length, which makes the struct a header for extra elements allocated behind it.