C binary read and write file

2019-09-22 06:39发布

问题:

I am using a binary file for reading an array of integers, then each even integer x should become 2 * x and each odd integer x should become 3 * x. When I am doing this it always read the 2nd integer (which is 2). Any idea?

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

int main(void)
{    
FILE *f;

f = fopen("inputData.txt", "w+b");
int n = 5;
int i;
for (i = 1; i <= n; ++i) {
    fwrite(&i, sizeof(int), 1, f);
}
int x;
fseek(f, 0, SEEK_SET);
while (fread(&x, sizeof(int), 1, f) == 1) {
    printf("%d ", x);
    if (x % 2 == 0) {
        fseek(f, -sizeof(int), SEEK_CUR);
        x = x * 2;
        fwrite(&x, sizeof(int), 1, f);
    } else {
        fseek(f, -sizeof(int), SEEK_CUR);
        x = 3 * x;
        fwrite(&x, sizeof(int), 1, f);
    }
}

fclose(f);
}

回答1:

Okay, I don't really understand what's going on, but it seems that you cannot trust fseek with SEEK_CUR when using with read/write files in that case (I'm running Windows, and the standard functions are notoriously different from Linux that may be the issue)

EDIT: Andrew's answer confirms my suspicions. My solution complies to what the standards recommend.

What I have done to workaround the problem is to manage file position myself and seek to that position instead of implicitly relying on current file position when calling fseek.

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

int main(void)
{
 FILE *f;

f = fopen("inputData.txt", "w+b");
if (!f) { perror("cannot create input"); exit(1); }

int n = 5;
int i;
for (i = 1; i <= n; ++i) {
    fwrite(&i, sizeof(int), 1, f);
}


int x;
int pos=0;
fseek(f, 0, SEEK_SET);
while (fread(&x, sizeof(int), 1, f) == 1) {
    if (fseek(f, pos, SEEK_SET)) {perror("cannot seek");exit(1);}
    pos += sizeof(int);
    printf("%d %lu\n", x, ftell(f));
    if (x % 2 == 0) {
        x = x * 2;
    } else {
        x = 3 * x;
    }
    if (fwrite(&x, sizeof(int), 1, f) != 1) {perror("cannot write");exit(1);}
    if (fseek(f, pos, SEEK_SET)) {perror("cannot seek");exit(1);}
}

fclose(f);
}

now the output of the program is (with current offset)

1 0
2 4
3 8
4 12
5 16

contents of the binary file is now (as expected on a little endian architecture):

03 00 00 00 04 00 00 00 09 00 00 00 08 00 00 00 0F 00 00 00

So this is a workaround but at least it works properly.



回答2:

This code:

while (fread(&x, sizeof(int), 1, f) == 1) {
    printf("%d ", x);
    if (x % 2 == 0) {
        fseek(f, -sizeof(int), SEEK_CUR);
        x = x * 2;
        fwrite(&x, sizeof(int), 1, f);
    } else {
        fseek(f, -sizeof(int), SEEK_CUR);
        x = 3 * x;
        fwrite(&x, sizeof(int), 1, f);
    }
}

violates the restrictions in 7.21.5.3 The fopen function, paragraph 7 of the C Standard (bolding mine):

When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end- of-file.

Each loop iteration ends with an fwrite() call, and the next loop iteration starts with fread() call, "without an intervening call to a file positioning function".



回答3:

f = fopen("inputData.txt", "w+b");

w truncates the file if it exists. + does not change this behavior.

Here is a simple demonstration.

$ cat test.c
#include <stdio.h>

int main() {
    FILE *f;

    f = fopen("inputData.txt", "w+b");
    if( f == NULL ) {
        perror("open failed");
    }

    fclose(f);
}

$ make
cc -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic -c -o test.o test.c
cc -fsanitize=address  test.o   -o test

$ cat inputData.txt 
abc
$ ./test
$ cat inputData.txt 

Instead, use r+b.


Two additional issues.

sizeof(int) returns an unsigned number, size_t. Your compiler will probably cast them to a signed long for you, but should do that explicitly.

fseek(f, -(long)sizeof(int), SEEK_CUR);

You're not checking if your file operations succeeded. In this case they are, but they will silently fail and be difficult to debug. It's important to check all your file operations.

if( fseek(f, -(long)sizeof(int), SEEK_CUR) != 0 ) {
    perror("fseek failed");
}