Structures & Functions
Structs interact with functions in three ways: passed in by value, passed in by pointer, and returned by value. Each has a different cost and a different effect on the caller's data. Choosing the right one is mostly about size and mutability.
Passing a struct by value
When a struct is passed by value, the function receives a complete, independent copy. Changes made to the parameter inside the function have no effect on the caller's original struct — exactly like passing an int or a double by value.
#include <stdio.h>
struct Point {
int x;
int y;
};
void tryToModify(struct Point p) {
p.x = 999; /* only changes the local copy */
}
int main(void) {
struct Point pt = {1, 2};
tryToModify(pt);
printf("pt.x is still %d\n", pt.x);
return 0;
}pt.x is still 1
Passing a struct by pointer
Passing a pointer avoids the copy and, as covered on the previous page, lets the function modify the caller's actual struct. This is the idiomatic choice for anything beyond a small, cheap-to-copy struct — and it is the only option when the function's whole purpose is to mutate the struct it's given.
#include <stdio.h>
struct Point {
int x;
int y;
};
void modify(struct Point *p) {
p->x = 999; /* changes the caller's actual struct */
}
int main(void) {
struct Point pt = {1, 2};
modify(&pt);
printf("pt.x is now %d\n", pt.x);
return 0;
}pt.x is now 999
Returning a struct by value
C also allows a function to return a struct by value. This is completely legal and often convenient — for example, a makePoint constructor-style function that builds and returns a fully initialized struct. Just like a by-value parameter, the returned struct is a full copy handed back to the caller.
#include <stdio.h>
struct Point {
int x;
int y;
};
struct Point makePoint(int x, int y) {
struct Point p = { x, y };
return p; /* a copy of p is returned to the caller */
}
int main(void) {
struct Point origin = makePoint(0, 0);
struct Point p = makePoint(4, 9);
printf("origin = (%d, %d)\n", origin.x, origin.y);
printf("p = (%d, %d)\n", p.x, p.y);
return 0;
}origin = (0, 0) p = (4, 9)
Side-by-side comparison
Approach | What is copied | Can modify caller? | When to use |
|---|---|---|---|
Pass by value | The entire struct, into the parameter | No | Small structs, read-only use |
Pass by pointer | Just the address (4–8 bytes) | Yes | Large structs, or when mutation is needed |
Return by value | The entire struct, out of the function | N/A — produces a new value | Building/constructing a struct to hand back |
A worked example using both approaches
#include <stdio.h>
struct Rectangle {
double width;
double height;
};
/* By value: read-only calculation, small struct, no mutation needed. */
double area(struct Rectangle r) {
return r.width * r.height;
}
/* By pointer: mutates the caller's struct directly. */
void scale(struct Rectangle *r, double factor) {
r->width *= factor;
r->height *= factor;
}
int main(void) {
struct Rectangle rect = {4.0, 5.0};
printf("Area before scaling: %.1f\n", area(rect));
scale(&rect, 2.0);
printf("Area after scaling: %.1f\n", area(rect));
return 0;
}Area before scaling: 20.0 Area after scaling: 40.0
Pass by value when the struct is small and the function only needs to read it.
Pass by pointer when the struct is large, or the function needs to modify the caller's copy.
Returning a struct by value is legal and common for constructor-style functions.
Never return a pointer to a local struct variable — its storage ends when the function returns.