2

I write this C code so that I could test whether fwrite could update some values in a text file. I tested on Linux and it works fine. In Windows (vista 32bits), however, it simply does not work. The file remains unchanged after I write a different byte using: cont = fwrite(&newfield, sizeof(char), 1, fp);

The registers are written on the file using a "@" separator, in the format:

Reg1FirstField@Reg1SecondField@Reg2FirstField@Reg2SecondField...

The final file should be: First@1@Second@9@Third@1@

I also tried putc and fprintf, all with no result. Can someone please help me with this?

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

typedef struct test {
char field1[20];
char field2;
} TEST;

int main(void) {
    FILE *fp;
    TEST reg, regread;
    char regwrite[22];
    int i, cont, charwritten;

    fp=fopen("testupdate.txt","w+");

    strcpy(reg.field1,"First");
    reg.field2 = '1';
    sprintf(regwrite,"%s@%c@", reg.field1, reg.field2);
    cont = (int)strlen(regwrite);
    charwritten = fwrite(regwrite,cont,1,fp);
    fflush(fp);

    strcpy(reg.field1,"Second");
    reg.field2 = '1';
    sprintf(regwrite,"%s@%c@", reg.field1, reg.field2);
    cont = (int)strlen(regwrite);
    charwritten = fwrite(regwrite,cont,1,fp);
    fflush(fp);

    strcpy(reg.field1,"Third");
    reg.field2 = '1';
    sprintf(regwrite,"%s@%c@", reg.field1, reg.field2);
    cont = (int)strlen(regwrite);
    charwritten = fwrite(regwrite,cont,1,fp);
    fflush(fp);
    fclose(fp);

    // open file to update
    fp=fopen("testupdate.txt","r+");

    printf("\nUpdate field 2 on the second register:\n");
    char aux[22];
    // search for second register and update field 2
    for (i = 0; i < 3; i ++) {
       fscanf(fp,"%22[^@]@", aux);
       printf("%d-1: %s\n", i, aux);
       if (strcmp(aux, "Second") == 0) {
            char newfield = '9';
            cont = fwrite(&newfield, sizeof(char), 1, fp);
            printf("written: %d bytes, char: %c\n", cont, newfield);
            // goes back one byte in order to read properly 
            // on the next fscanf
            fseek(fp,-1,SEEK_CUR);
        } 
        fscanf(fp,"%22[^@]@", aux);
        printf("%d-2: %s\n",i, aux);
        aux[0] = '\0';
}
fflush(fp);
fclose(fp);

// open file to see if the update was made
fp=fopen("testupdate.txt","r");
for (i = 0; i < 3; i ++) {
   fscanf(fp,"%22[^@]@", aux);
   printf("%d-1: %s\n", i, aux);
   fscanf(fp,"%22[^@]@",aux);
   printf("%d-2: %s\n",i, aux);
   aux[0] = '\0';
}
fclose(fp);
getchar();

return 0;
}
Moacir Ponti
  • 591
  • 5
  • 14
  • Your `aux` has space for **21** "regular" characters and the null terminator. The conversion specification in `scanf` should be `%21[^@]` – pmg Jun 06 '11 at 18:59

5 Answers5

3

I didn't know it but here they explain it: why fseek or fflush is always required between reading and writing in the read/write "+" modes

Conclusion: You must either fflush or fseek before every write when you use "+".

fseek(fp, 0, SEEK_CUR);
// or
fflush(fp);

cont = fwrite(&newfield, sizeof(char), 1, fp);

Fix verified on Cygwin.

Community
  • 1
  • 1
LatinSuD
  • 1,779
  • 12
  • 19
  • Apparently if you read, then write, you need to use a file-positioning function specifically, not just one or the other. It's only for write-then-read that `fflush()` can be used instead. – JAB Jun 06 '11 at 19:05
  • The buffering by the stream functions must be why the calls to `fgetpos` and `fsetpos` also worked in my updated answer below ... – Jason Jun 06 '11 at 19:17
  • Well, thank you @Jason, @LatinSud and @pmg for aswering my question! I've learned a lot. – Moacir Ponti Jun 06 '11 at 20:10
3

You're missing a file positioning function between the read and write. The Standard says:

7.19.5.3/6

When a file is opened with update mode, both input and output may be performed on the associated stream. However, ... 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. ...

for (i = 0; i < 3; i ++) {
   fscanf(fp,"%22[^@]@", aux);                              /* read */
   printf("%d-1: %s\n", i, aux);
   if (strcmp(aux, "Second") == 0) {
        char newfield = '9';

        /* added a file positioning function */
        fseek(fp, 0, SEEK_CUR);                             /* don't move */

        cont = fwrite(&newfield, sizeof(char), 1, fp);      /* write */
pmg
  • 106,608
  • 13
  • 126
  • 198
0

You're not checking any return values for errors. I'm guessing the file is read-only and is not even opening properly.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • The file was written correctly, it just does not overwrite the value '1' with the value '9'. Also, I checked and the file was not saved with read-only permission. – Moacir Ponti Jun 06 '11 at 17:39
0

At least here on OSX, your value 9 is begin appended to the end of the file ... so you're not updating the actual register value for Second at it's position in the file. For some reason after the scan for the appropriate point to modify the values, your stream pointer is actually at the end of the file. For instance, running and compiling your code on OSX produced the following output in the actual text file:

First@1@Second@1@Third@1@9

The reason your initial read-back is working is because the data is being written, but it's at the end of the file. So when you write the value and then back-up the stream and re-read the value, that works, but it's not being written in the location you're assuming.

Update: I've added some calls to ftell to see what's happening to the stream pointer, and it seems that your calls to fscanf are working as you'd assume, but the call to fwrite is jumping to the end of the file. Here's the modified output:

Update field 2 on the second register:
**Stream position: 0
0-1: First
0-2: 1
**Stream position: 8
1-1: Second
**Stream position before write: 15
**Stream position after write: 26
written: 1 bytes, char: 9
1-2: 9
**Stream position after read-back: 26

Update-2: It seems by simply saving the position of the stream-pointer, and then setting the position of the stream-pointer, the call to 'fwrite` worked without skipping to the end of the file. So I added:

fpos_t position;
fgetpos(fp, &position);
fsetpos(fp, &position);

right before the call to fwrite. Again, this is on OSX, you may see something different on Windows.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • The file pointer is in the write spot. I know it because if I substitute the line: `cont = fwrite(&newfield, sizeof(char), 1, fp);` by the line `fscanf(fp,"%c", &newfield);`, it reads exactly the '1' character I want to overwrite. Here in Linux the final text file is: `First@1@Second@9@Third@1@`, but in Windows is `First@1@Second@1@Third@1@`. Thanks anyway for your contribution. – Moacir Ponti Jun 06 '11 at 17:56
  • Yes, you're stream pointer is in the correct spot *before* the write, but not after the write ... at least not on OSX. For some reason it seems to be treating the "r+" flag like an "a+" flag for the call to `fopen`. Obviously there are some odd platform-variations happening here. – Jason Jun 06 '11 at 18:06
-2

With this:

fp=fopen("testupdate.txt","w+");
                            ^------ Notice the + sign

You opened the file in "append" mode -- that is what the plus sign does in this parameter. As a result, all of your fwrite() calls will be relative to the end of the file.

Using "r+" for the fopen() mode doesn't make sense -- the + means nothing in this case.

This and other issues with fopen() are why I prefer to use the POSIX-defined open().

To fix your particular case, get rid of the + characters from the fopen() modes, and consider that you might need to specify binary format on Windows ("wb" and "rb" modes).

Heath Hunnicutt
  • 18,667
  • 3
  • 39
  • 62
  • No, `a` or `a+` would be appending to the end of the file. `w+` means reading+writing, but overwriting an existing file. And `r+` indicates both reading and writing without any clearing of the original file. – JAB Jun 06 '11 at 18:47