CStructures & Functions

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.

C
#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
Copies are not free
Every call to a function that takes a struct by value copies the entire struct — every field, every byte. For a two-int `Point` that is negligible. For a struct containing large arrays or many fields, that copy happens on every single call and can noticeably hurt performance, especially inside a hot loop. Measure before assuming it's fine, but default to pointers for anything beyond a couple of small fields.
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.

C
#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.

C
#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)
Returning by pointer is riskier than it looks
A function must never return a pointer to a local struct variable — that variable's storage no longer exists once the function returns, so the pointer would be dangling. If a struct needs to outlive the function that creates it and be handed back via a pointer, it must be allocated with `malloc` (heap memory), or the caller must supply the storage itself by passing in a pointer to fill.
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

C
#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.

Next: collections of structs
The next page combines structs with arrays, showing how to model a whole collection of records — like a roster of students — and loop over them efficiently.