Skip to Content
Course content

228: Building a Simple Banking System with File Persistence

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, our programs have been "forgetful." Every time you hit Ctrl+C or the program finishes, all your data vanishes into the ether. In a real-world scenario—like a banking system—that's a disaster. We need a way to make our data persist across sessions. Today, we're going to build a rudimentary banking system that saves account records to a binary file.

Defining our account structure

First, we need a way to represent an account. I'm going to use a struct because it keeps the account ID, the owner's name, and the balance bundled together. This makes it much easier to write a single block of memory to a file rather than managing three separate files or a messy CSV.

typedef struct {
    int id;
    char name[50];
    double balance;
} Account;

I'm using a fixed-size array for the name. I'll explain why in a moment, but for now, just know that in C, fixed sizes are your best friend when you're dealing with binary file I/O.

Dumping data to the disk

To save our data, we'll use fwrite. Unlike fprintf, which converts numbers to text, fwrite just grabs a chunk of memory and dumps it exactly as it is onto the disk. It's faster and more efficient for structured data.

void save_accounts(Account accounts[], int count) {
    FILE *file = fopen("bank.dat", "wb");
    if (file == NULL) {
        perror("Error opening file for writing");
        return;
    }
    fwrite(accounts, sizeof(Account), count, file);
    fclose(file);
}

Notice the "wb" mode. The 'b' stands for binary. If you're on Windows and forget that 'b', the runtime might try to "help" you by converting newline characters, which will absolutely corrupt your binary data.

The pointer persistence trap

Now, here is where I almost messed up this build. In an earlier draft of this code, I tried to be "efficient" and defined the name as a char *name instead of char name[50]. I thought, "Why waste 50 bytes if the name is only 5 letters?"

The problem is that a pointer is just a memory address. When I used fwrite, I wasn't saving the name "Alice"; I was saving the hexadecimal address 0x7ffc1234. When I restarted the program and read that address back, it pointed to a piece of memory that no longer contained the name—or worse, it pointed to memory the program didn't own, leading to a segmentation fault.

The fix is simple: always use fixed-size arrays inside structs that you intend to write to disk. This ensures the actual data is stored inside the struct, not somewhere else in the heap.

Restoring the bank on startup

To get our data back, we use fread. We need to make sure we have enough space allocated in our array to hold whatever is in the file. For this simple example, we'll assume a maximum of 100 accounts.

int load_accounts(Account accounts[]) {
    FILE *file = fopen("bank.dat", "rb");
    if (file == NULL) {
        return 0; // No file exists yet, which is fine for the first run
    }
    int count = fread(accounts, sizeof(Account), 100, file);
    fclose(file);
    return count;
}

In a production system, you'd probably store the number of records at the very beginning of the file so you don't have to guess the count or hardcode a limit, but for our purposes, this gets the job done.

Wiring it all together

Let's put this into a small loop. We'll load the accounts, allow the user to add one, and then save it back. This creates a persistent loop where the data survives the program's death.

int main() {
    Account accounts[100];
    int count = load_accounts(accounts);

    printf("Loaded %d accounts.\n", count);

    if (count < 100) {
        accounts[count].id = count + 1;
        printf("Enter name for new account: ");
        scanf("%s", accounts[count].name);
        accounts[count].balance = 0.0;
        count++;
    }

    save_accounts(accounts, count);
    printf("Bank state saved. Total accounts: %d\n", count);
    return 0;
}

It's a basic flow, but it demonstrates the core cycle of persistence: Load → Modify → Save.




📋 Practical Task

Exercise: Implementing a Balance Update Feature

Your task is to extend the banking system we just built. Currently, the program only adds a new account every time it runs. You need to modify the program to allow the user to update the balance of an existing account and ensure that change is persisted to the file.

Requirements:

  • Modify the main function to ask the user for an Account ID.
  • Search the accounts array for that ID.
  • If found, prompt the user for a deposit amount and add it to the balance.
  • Call save_accounts after the update to ensure the new balance is written to bank.dat.
  • If the ID is not found, print a friendly error message.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.