C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
63: Passing Structs to Functions
If you've spent any time with higher-level languages like Java, Python, or C#, you've likely developed a habit of assuming that when you pass an "object" into a function, you're passing a reference to that object. You might assume that if you change a property of that object inside the function, the change sticks. In C, this is a dangerous assumption when it comes to structs.
Thinking Structs are Passed by Reference by Default
Let's look at a common mistake. Imagine we're building a simple RPG and we have a Player struct. You want a function that handles taking damage. It feels natural to write it like this:
typedef struct {
char name[50];
int health;
int level;
} Player;
void takeDamage(Player p, int amount) {
p.health -= amount;
printf("Inside function: %s took %d damage. Health is now %d\n", p.name, amount, p.health);
}
int main() {
Player hero = {"Aragorn", 100, 10};
takeDamage(hero, 20);
printf("Outside function: %s health is %d\n", hero.name, hero.health);
return 0;
}
If you run this, you'll see something frustrating: the output says health is 80 inside the function, but it's still 100 outside. Why? Because in C, structs are passed by value. When you call takeDamage(hero, 20), C creates a bit-for-bit copy of the entire hero struct and hands that copy to the function. You aren't modifying Aragorn; you're modifying a temporary clone of Aragorn that gets destroyed the moment the function returns.
Updating State with Pointers and the Arrow Operator
To actually modify the original struct, you have to pass a pointer to it. This tells C, "Don't copy the whole data structure; just tell the function where the original lives in memory."
Once you pass a pointer, you can't use the dot (.) operator directly on the pointer because the pointer is just a memory address, not the struct itself. You could dereference the pointer first—(*p).health—but that's clunky and annoying to write. Instead, we use the arrow operator (->), which is essentially shorthand for "dereference this pointer and access this member."
void takeDamage(Player *p, int amount) {
// p is now a pointer to the original Player struct
p->health -= amount;
printf("Inside function: %s took %d damage. Health is now %d\n", p->name, amount, p->health);
}
int main() {
Player hero = {"Aragorn", 100, 10};
// Pass the address of hero using the & operator
takeDamage(&hero, 20);
printf("Outside function: %s health is %d\n", hero.name, hero.health);
return 0;
}
Now, the health is 80 in both places. We've modified the original memory.
The Hidden Cost of Pass-by-Value
Even if you don't need to modify the struct, you should still think twice before passing by value. If your struct only has two integers, copying it is cheap. But what if your struct contains a large array or dozens of fields? Every time you pass that struct by value, C has to copy every single byte of that data onto the stack. It's a silent performance killer.
I generally follow this rule of thumb: if the struct is larger than a few words, pass a const pointer to it. Using const Player *p tells the compiler (and other programmers) that the function needs to see the data, but it isn't allowed to change it. You get the speed of a pointer with the safety of a value copy.
📋 Practical Task
Exercise: RPG Character Stat Modifier
You are tasked with creating a "Level Up" system for a game. You need to implement a function that modifies a character's stats when they gain a level.
Requirements:
- Define a struct named
Characterwith the following fields:char name[30],int level,int strength, andint agility. - Create a function called
levelUpthat takes a pointer to aCharacterand anintrepresenting the bonus points to be distributed. - Inside
levelUp, you must:- Increment the
levelby 1. - Add half of the bonus points to
strength. - Add the other half of the bonus points to
agility.
- Increment the
- In your
mainfunction, initialize aCharacter, print their stats, call thelevelUpfunction, and then print the stats again to prove the changes persisted.
There are no comments for now.