Stack vs. Heap in Practice
First, naive version:
#include <stdio.h>
#include <stdint.h>
// Some data structure.
struct player_t {
char* name;
uint32_t health;
};
// And a constructor to create an instance of it.
// |
// v should return (struct player_t) right?!
struct player_t create_player (char* name, uint32_t initial_health) {
struct player_t player;
player.name = name; // "Omg, this is like JAVA!"
player.health = initial_health;
return player;// So easy!
}
// Applies the specified amount of damage to the player.
void make_damage (struct player_t player, uint32_t damage) {
player.health -= damage;
}
void say_punchline (struct player_t player) {
printf("My name is %s and i have %u health remaining.\n", player.name, player.health);
printf("My balls, your face.\n");
}
int main () {
struct player_t main_character_guy = create_player("The Duke", 5000);
say_punchline(main_character_guy);
make_damage(main_character_guy, 1500);
say_punchline(main_character_guy);
return 0;
}
This suffers from the same problem as the naive swap(a, b) implementation: The players health is not actually affected by the make_damage(…) call. By implementing everything to work on pointers to struct player_t instead of directly on the structs, we solve this problem.
#include <stdio.h>
#include <stdint.h>
// Some data structure.
struct player_t {
char* name;
uint32_t health;
};
// And a constructor to create an instance of it.
struct player_t* create_player (char* name, uint32_t initial_health) {
struct player_t player;
player.name = name; // "Still JAVA!"
player.health = initial_health;
return &player; // Still easy!
}
// Applies the specified amount of damage to the player.
void make_damage (struct player_t* player, uint32_t damage) {
player->health -= damage;
}
void say_punchline (struct player_t* player) {
printf("My name is %s and i have %u health remaining.\n", player->name, player->health);
printf("My balls, your face.\n");
}
int main () {
struct player_t* main_character_guy = create_player("The Duke", 5000);
say_punchline(main_character_guy);
make_damage(main_character_guy, 1500);
say_punchline(main_character_guy);
return 0;
}
Unfortunately, this implementation has a different error. After calling make_damage(…) the data in the main_character_guy struct has become garbage. This is the classic example of losing data on the stack.
By switching to heap allocated memory for the player (using malloc), we obtain the correct version:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h> // <-- malloc comes frome here
// Some data structure.
struct player_t {
char* name;
uint32_t health;
};
// And a constructor to create an instance of it.
struct player_t* create_player (char* name, uint32_t initial_health) {
struct player_t* player_ptr = malloc(sizeof(struct player_t)); // Careful with assumptions about returned memory.
player_ptr->name = name;
player_ptr->health = initial_health;
return player_ptr;
}
// Applies the specified amount of damage to the player.
void make_damage (struct player_t* player, uint32_t damage) {
player->health -= damage;
}
void say_punchline (struct player_t* player) {
printf("My name is %s and i have %u health remaining.\n", player->name, player->health);
printf("My balls, your face.\n");
}
int main () {
struct player_t* main_character_guy = create_player("The Duke", 5000);
say_punchline(main_character_guy);
make_damage(main_character_guy, 1500);
say_punchline(main_character_guy);
free(main_character_guy);
return 0;
}