3

I'm writing a program that has a struct for bank clients (BankAccount) having 3 elements - account number, name of client, and bank balance. I have a file with records already in it, and I have a function to modify the balance. However, I am unable to do so. The balance remains the same. I think my fseek() is wrong, but I'm not sure how/why. I've used fflush(stdin) in some places where it may not be required, but I don't think that's related to the problem. I've commented to let you know my logic, just in case there's some misunderstanding in my concepts. Here's the code:

 void modify(){

    int account_number;
    FILE *ptr;
    BankAccount account;

    ptr = fopen("account.txt", "r+");
    printf("Enter account number: ");
    fflush(stdin);
    scanf("%d", &account_number);

    while (!feof(ptr)) // To search the whole "account.txt" file.
    {
        fread(&account, sizeof(BankAccount), 1, ptr); //brings record into memory
        if (account.account_number == account_number){ // if record's account number is same as account number entered by user above
            printf("***Account found***\n\nAccount number: %d\nAccount name: %s\nAccount balance: %.2f\n", account.account_number, account.name, account.balance);
            printf("\nEnter new balance: ");
            fflush(stdin);
            scanf("%f", &account.balance); // rewrites account's balance in memory
            fseek(ptr, -sizeof(BankAccount), SEEK_CUR); //pointer seeked to the beginning of the record to overwrite it with the one in memory
            fwrite(&account,sizeof(BankAccount), 1, ptr); // record overwritten
            return;

        }
    }


    printf("Account not found\n");
    fflush(stdin);
    getch();
}

Here's the whole .cpp file of my project in case you'd like to run it : Source code. I'd appreciate some guidance. Thanks in advance.

mmahmad
  • 33
  • 6
  • C files don't have any records (or some other structures). They are just a stream (or a sequence) of bytes. Did you consider [GDBM](http://www.gnu.org.ua/software/gdbm/) or [SQlite](http://www.sqlite.org/) ? On what operating system is your code running? – Basile Starynkevitch Dec 22 '13 at 12:20
  • @BasileStarynkevitch I'm a freshman studying CS. We've just started learning FileIO in C, so I've no idea about GDBM or SQlite (though I think they've got something to do with databases and stuff?) Running Windows 8. – mmahmad Dec 22 '13 at 12:29
  • @mbratch Yes, I am able to see "***Account found***" along with the account details. I am also able to take input for the new balance, but the balance isn't overwritten when I print the record again (have another function for that). And, I'm sorry about the binary stuff, don't understand that. What exactly do you mean by "reading/writing in binary"? – mmahmad Dec 22 '13 at 12:31
  • `while(!feof(ptr))` abuse warning... this while loop states: _"While we haven't read _past_ the EOF, and no errors occurred while reading do ..."_ – Elias Van Ootegem Dec 22 '13 at 12:39
  • @EliasVanOotegem Umm... I don't understand. Is it wrong to use this? I want the loop to run until it account.number == account_number. For this, it has to search all the records in the file, right? Is there any other way to do it? – mmahmad Dec 22 '13 at 12:48
  • See [Why `while (!feof(file))` is always wrong](http://stackoverflow.com/questions/5431941/while-feof-file-is-always-wrong) for a discussion of why your loop structure is wrong. You aren't error checking your open or read operations; that is always a bad idea. – Jonathan Leffler Dec 22 '13 at 13:41

1 Answers1

2

The problem could be the fseek() call:

fseek(ptr, -sizeof(BankAccount), SEEK_CUR);

The return value from sizeof() is an unsigned type; the negation of it is going to be a very large number. Technically, that's incorrect (fseek() should get a long, not a size_t). However, if sizeof(size_t) == sizeof(long), you get away with it (it works for me).

Another aspect of the problem could be that you don't close the file before you return (whether you find the record or not). That's most certainly a memory leak; it might also affect how the data is written to disk. This is probably the root cause of your trouble. You have another function the opens the file to read the data to see the changes, but since the file is not closed, the data has not been written to disk. Note: Since the source is now available, this is the cause of the problems.

Since you don't show us the data structure, another problem could be a mismatch of type float vs double for the balance member; equally, the only problem might be that float is an inappropriate type for account balances (it can't reliably represent balances above about $100,000.00 to the nearest cent, for example — e.g. entering 199999.99 is displayed as 199999.98).

Note: On modern versions of Linux and on Windows, fflush(stdin) is a defined operation (and the defined behaviour is sensible and useful). According to the C standard and POSIX, it yields undefined behaviour. Be cautious about using it — be aware that it is not a portable operation.

Converted into an SSCCE (Short, Self-Contained, Correct Example), a very slight modification of your code (adding fclose()) works for me:

#include <stdio.h>
#include <stdlib.h>

typedef struct BankAccount
{
    int account_number;
    char   name[20];
    float balance;
} BankAccount;

static void modify(void)
{
    int account_number;
    FILE *ptr;
    BankAccount account;

    ptr = fopen("account.txt", "r+");
    printf("Enter account number: ");
    fflush(stdin);
    scanf("%d", &account_number);

    while (!feof(ptr))
    {
        fread(&account, sizeof(BankAccount), 1, ptr);
        printf("***Account read***(%d: %s: %.2f)\n", 
               account.account_number, account.name, account.balance);
        if (account.account_number == account_number)
        {
            printf("***Account found***\n\nAccount number: %d\nAccount name: %s\nAccount balance: %.2f\n", account.account_number, account.name, account.balance);
            printf("\nEnter new balance: ");
            fflush(stdin);
            scanf("%f", &account.balance);
            fseek(ptr, -sizeof(BankAccount), SEEK_CUR);
            fwrite(&account, sizeof(BankAccount), 1, ptr);
            fclose(ptr);
            return;
        }
    }

    printf("Account not found\n");
    fflush(stdin);
    fclose(ptr);
}

static void write(void)
{
    FILE *fp = fopen("account.txt", "w");
    if (fp == 0)
    {
        fprintf(stderr, "Create file failed\n");
        exit(1);
    }
    static const BankAccount data[] =
    {
        { 1, "His", 20.00 },
        { 2, "Hers", 2000.00 },
        { 3, "Theirs", 1.00 },
    };
    if (fwrite(data, sizeof(data), 1, fp) != 1)
    {
        fprintf(stderr, "Write file failed\n");
        exit(1);
    }
    fclose(fp);
}

static void read(void)
{
    FILE *fp = fopen("account.txt", "r");
    if (fp == 0)
    {
        fprintf(stderr, "Open file failed\n");
        exit(1);
    }
    BankAccount ac;
    while (fread(&ac, sizeof(ac), 1, fp) == 1)
    {
        printf("A/C: %4d %-20s  %8.2f\n", ac.account_number, ac.name, ac.balance);
    }
    fclose(fp);
}

int main(void)
{
    write();
    read();
    modify();
    read();
    return 0;
}

The compiler doesn't even witter about the conversion on the fseek() with the compiler options:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
      -Wold-style-definition -Werror ba.c -o ba

When run, it shows:

A/C:    1 His                      20.00
A/C:    2 Hers                   2000.00
A/C:    3 Theirs                    1.00
Enter account number: 2
***Account read***(1: His: 20.00)
***Account read***(2: Hers: 2000.00)
***Account found***

Account number: 2
Account name: Hers
Account balance: 2000.00

Enter new balance: 4000
A/C:    1 His                      20.00
A/C:    2 Hers                   4000.00
A/C:    3 Theirs                    1.00

Note the correct form for checking whether you've reached the end of file in the function read(). If you call feof(), you are doing it wrong 99.9% of the time.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks for the debugging! I completely forgot fclose(), although I did use it on my other functions. Made me realize how important this is. Also, didn't know that my way of checking the EOF was wrong, thanks for that too. I use the command fflush(stdin) whenever I scanf() or gets(), otherwise it causes problems. I used it here more often than I ought to, so thanks for warning me about that too. – mmahmad Dec 22 '13 at 14:40
  • 1
    You're welcome. Note that there are some other minor problems, such as repeating the file name in each of three functions; that should be defined once in a variable or constant, and the variable passed to the various functions. My code uses `write()` and `read()`; they're also function names defined by POSIX. The code as written is safe (and works on POSIX-ish systems), but should really use other names. I didn't upgrade your code to error check the `scanf()` call; it should be checked — all input (and open) operations should be checked. Be wary of using `getch()` without a prompt. – Jonathan Leffler Dec 22 '13 at 14:59