Reading and writing binary files in C
Asked Answered
C

2

7

These are 2 separate applications.

  • In the first one, I tried to store employee details like name, age and salary in the binary file named emp.bin.
  • In the second application, I tried to view the contents of the file but in place of the name, only the first character appears.

I tried printing each character separately, and it turns out that there's 3 null characters '\n' after each letter in the name that is why it is not printing after the first character.

"Write" application code:

//Receives records from keyboard and writes them to a file in binary mode
#include <stdio.h>

int main()
{
    FILE *fp;
    char another = 'Y';
    struct emp
    {
        char name[40];
        int age;
        float bs;
    };
    struct emp e;
    fp = fopen("emp.bin", "wb");
    if (fp == NULL)
    {
        puts("Cannot open the file.");
        return 1;
    }
    while(another == 'Y')
    {
        printf("Enter the employee name, age and salary: ");
        scanf("%S %d %f", e.name, &e.age, &e.bs);
        while(getchar() != '\n');
        fwrite(&e, sizeof(e), 1, fp);
        printf("Add another record? (Y/N)");

        another = getchar();
    }
    fclose(fp);
    return 0;
}

"Read" application code:

//Read records from binary file and displays them on VDU
#include <stdio.h>
#include <ctype.h>

int main()
{
    FILE *fp;
    struct emp
    {
        char name[40];
        int age;
        float bs;
    } e;
    fp = fopen("emp.bin", "rb");
    if (fp == NULL)
    {
        puts("Cannot open the file.");
        return 1;
    }
    while (fread(&e, sizeof(e), 1, fp) == 1)
    {
        printf("\n%s \t %d \t $%.2f\n", e.name, e.age, e.bs);
    }
    fclose(fp);
}

Here's the input and output:

enter image description here enter image description here

How can I correct this code to make it print the whole name?

Civics answered 22/5, 2020 at 14:44 Comment(3)
Good job stating the problem concisely and showing brief code that demonstrates the problem. Excellent first question.Hideandseek
I didn't mention it in my answer because it is probably a typo (and because I noticed it while editing) but when you say_"3 null characters '\n'"_ there is a mistake: \n is the line feed character; the nul character is \0 (the string terminator) and it is actually what you get (I reproduced your issue).Harr
while(getchar() != '\n'); will cause an infinite loop in case of an early end of file.Scintillometer
H
3

The problem is in the "writer" application, even before the actual write is performed.

When you get data from the user

scanf("%S %d %f", e.name, &e.age, &e.bs);

you use format %S (capital letter "S". Format specifiers are case sensitive!). As we can read in the printf man page

S
(Not in C99, but in SUSv2.) Synonym for ls. Don't use.

this leads us to %ls format specifier that is described in the following way

s
[...] If an l modifier is present: The const wchar_t * argument is expected to be a pointer to an array of wide characters. Wide characters from the array are converted to multibyte characters

Talking about Windows source we have:

S
Opposite-size character string, up to first white-space character (space, tab or newline). [...]
When used with scanf functions, signifies wide-character array; when used with wscanf functions, signifies single-byte-character array [...]

So, basically, you are reading characters from stdin and converting them to wide chars. In this case every character takes sizeof(wchar_t). Probably in your system this size is 4.


What you need is simply %s format specifier. And since your name array has size 40, I suggest using

scanf("%39s", e.name );

to get the name from user. In this way up to 39 characters will be written, being the 40th reserved to the string terminator '\0'.

Harr answered 22/5, 2020 at 15:6 Comment(6)
@chux copy and paste typo. Thank you.Harr
@RobertoCaboni: good analysis, except for In this case every character takes four bytes: the actual size of wchar_t is implementation defined, it could be 2 as is the case on Windows.Scintillometer
Should I add a section about consuming characters after 39th limit is reached?Harr
@Scintillometer know what? I thought the same before testing the program on my windows (64bits) machine..Harr
@RobertoCaboni: I shoud have just written: it could be 2 on legacy systems.Scintillometer
... or "In this case every character takes sizeof(wchar_t) bytes."Mathematician
S
1

As noted by Roberto in his answer, the problem is the %S conversion specifier, which is a typo, you should use %s.

Note however that there are other issues which might pose problems:

  • you should tell scanf() the maximum number of characters to read for the employee name, otherwise scanf() may write beyond the end of the destination array if input is too long.

  • if both programs run on separate systems with different endianness, the numbers will be incorrect on the receiving end because their bytes will be stored in the opposite order. For this reason, endianness should be specified and handled explicitly in binary files. Text format tends to be preferred for data transmission.

Here is a modified version:

//Receives records from keyboard and writes them to a file in binary mode
#include <stdio.h>

int main() {
    FILE *fp;
    char another = 'Y';
    struct emp {
        char name[40];
        int age;
        float bs;
    } e;
    int c;

    fp = fopen("emp.bin", "wb");
    if (fp == NULL) {
        puts("Cannot open the file.");
        return 1;
    }
    while (another == 'Y') {
        printf("Enter the employee name, age and salary: ");
        if (scanf("%39s %d %f", e.name, &e.age, &e.bs) != 3)
            break;
        while ((c = getchar()) != EOF && c != '\n')
            continue;
        if (fwrite(&e, sizeof(e), 1, fp) != 1)
            break;
        printf("Add another record? (Y/N)");
        another = getchar();
    }
    fclose(fp);
    return 0;
}

"Read" application code:

//Read records from binary file and displays them on VDU
#include <stdio.h>
#include <ctype.h>

int main() {
    FILE *fp;
    struct emp {
        char name[40];
        int age;
        float bs;
    } e;
    fp = fopen("emp.bin", "rb");
    if (fp == NULL) {
        puts("Cannot open the file.");
        return 1;
    }
    while (fread(&e, sizeof(e), 1, fp) == 1) {
        printf("\n%s \t %d \t $%.2f\n", e.name, e.age, e.bs);
    }
    fclose(fp);
    return 0;
}
Scintillometer answered 22/5, 2020 at 17:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.