-4

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);
}
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219

3 Answers3

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.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • I'm going to be a bit of a broken record here: check the return value of your file operations. – Schwern Nov 16 '18 at 21:28
  • Yes, there are [issues with `fseek` on Windows](https://msdn.microsoft.com/en-us/library/75yw9bf3.aspx), but only for reading text. Out of curiosity is it any different with `wb+`, the `+` after the `b`? – Schwern Nov 16 '18 at 21:33
  • Yes, text files have a broken ftell/fseek because of carriage returns being removed when read. I didn't believe what I saw when I first ran OP code. I've used fseek successfully with binary files but they weren't read/write. I think the mix between read & write confuses fseek, probably buffering stuff... – Jean-François Fabre Nov 16 '18 at 21:34
  • @Schwern There are no "issues" with `fseek()` on Windows. Per [**7.21.9.4 The ftell function**, paragraph 2 of the C standard](https://port70.net/~nsz/c/c11/n1570.html#7.21.9.4p2): "For a text stream, its file position indicator contains unspecified information, usable by the fseek function for returning the file position indicator for the stream to its position at the time of the ftell call; the difference between two such return values is not necessarily a meaningful measure of the number of characters written or read." – Andrew Henle Nov 16 '18 at 21:49
  • (cont) The only strictly compliant way to use `fseek()` on a text stream is to use it to return to a location returned from a previous call to `ftell()`. – Andrew Henle Nov 16 '18 at 21:49
  • @AndrewHenle the text file is another story. This is a completely different case, where fwrite & fread change the file position in an unexpected way, making further calls to ftell or fseek (relative) unreliable – Jean-François Fabre Nov 16 '18 at 22:15
1

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".

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
0
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");
}
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • that's not the only issue, `-sizeof(int)` cannot be used without conversion. At this point, it'd be better to close as no mcve or duplicate – Jean-François Fabre Nov 16 '18 at 21:00
  • I still have the same error after changing in "r+b". Check edit – Mihai Myhai Nov 16 '18 at 21:05
  • @MihaiMyhai Works For Me™ Be sure to check the return value of *all* of your file operations. – Schwern Nov 16 '18 at 21:11
  • Well, I used all suggestions you gave to me, but no one worked. It always prints 2 and I can't find out why. Maybe are the textEditor/ compiler fault? – Mihai Myhai Nov 16 '18 at 21:12
  • @Schwern the returned value of all my file operations are 2 – Mihai Myhai Nov 16 '18 at 21:15
  • Running your code prints `1 2 3 4 5 ` and hexdumping `inputData.txt` is `03 00 00 00 04 00 00 00 09 00 00 00 08 00 00 00 0f 00 00 00` as expected. `fseek` should return non-zero only on failure. You'll have to show us your complete code and how you're compiling and running it. – Schwern Nov 16 '18 at 21:17
  • Well this is actually my entire code ( excepting the int main() and return 0) . The program prints 2 because it always loop to the 2nd element from the list (1, 2, 3, 4, 5). – Mihai Myhai Nov 16 '18 at 21:20
  • @MihaiMyhai Then you're not checking your file operations. Check your file operations. `fseek` should return 0 on success. `fwrite` returns the number of items. `fopen` returns non-null. If they're failing and you're on Windows, do you have `inputData.txt` open in an editor? WIndows has mandatory file locking. – Schwern Nov 16 '18 at 21:21