Danson
Danson

Reputation: 425

C Read and replace char

I'm trying to read a file and replace every char by it's corresponding char up one in ASCII table. It opens the file properly but keep on reading the first character.

int main(int argc, char * argv[])
{
    FILE *input;
    input = fopen(argv[2], "r+");
    if (!input)
    {
        fprintf(stderr, "Unable to open file %s", argv[2]);
        return -1;
    }

    char ch;
    fpos_t * pos;
    while( (ch = fgetc(input)) != EOF)
    {
            printf("%c\n",ch);
            fgetpos (input, pos);
            fsetpos(input, pos-1);
            fputc(ch+1, input);
    }
    fclose(input);
    return 1;
}

the text file is

abc
def
ghi

I'm pretty sure it's due to the fgetpos and fsetpos but if I remove it then it will add the character at the end of the file and the next fgetc will returns EOF and exit.

Upvotes: 2

Views: 4482

Answers (5)

BLUEPIXY
BLUEPIXY

Reputation: 40145

procedure for performing random access such

  1. positioned the record
  2. reading of the record
  3. positioned the record
  4. update(write) the record
  5. do flush (to finalize the update)

The following code is a rewrite in consideration to it.

#include <stdio.h>
#include <ctype.h>

int main(int argc, char * argv[]){
    FILE *input;
    input = fopen(argv[1], "rb+");
    if (!input){
        fprintf(stderr, "Unable to open file %s", argv[1]);
        return -1;
    }

    int ch;
    fpos_t pos, pos_end;
    fgetpos(input, &pos);
    fseek(input, 0L, SEEK_END);
    fgetpos(input, &pos_end);
    rewind(input);
    while(pos != pos_end){
        ch=fgetc(input);
        if(EOF==ch)break;
        printf("%c",ch);
        if(!iscntrl(ch) && !iscntrl(ch+1)){
            fsetpos(input, &pos);
            fputc(ch+1, input);
            fflush(input);
        }
        pos += 1;
        fsetpos(input, &pos);
    }
    fclose(input);
    return 1;
}

Upvotes: 2

autistic
autistic

Reputation: 15642

7.21.9.1p2

The fgetpos function stores the current values of the parse state (if any) and file position indicator for the stream pointed to by stream in the object pointed to by pos. The values stored contain unspecified information usable by the fsetpos function for repositioning the stream to its position at the time of the call to the fgetpos function.

The words unspecified information don't seem to inspire confidence in that subtraction. Have you considered calling fgetpos prior to reading the character, so that you don't have to do a non-portable subtraction? Additionally, your call to fgetpos should probably pass a pointer to an existing fpos_t (eg. using the &address-of operator). Your code currently passes a pointer to gibberish.

fgetc returns an int, so that it can represent every possible unsigned char value distinct from negative EOF values.

Suppose your char defaults to an unsigned type. (ch = fgetc(input)) converts the (possibly negative, corresponding to errors) return value straight to your unsigned char type. Can (unsigned char) EOF ever compare equal to EOF? When does your loop end?

Suppose your char defaults, instead, to a signed type. (c = fgetc(input)) is likely to turn the higher range of any returned unsigned char values into negative numbers (though, technically, this statement invokes undefined behaviour). Wouldn't your loop end prematurely (eg. before EOF), in some cases?

The answer to both of these questions indicates that you're handing the return value of fgetc incorrectly. Store it in an int!

Perhaps your loop should look something like:

for (;;) {
    fpos_t p;

    /* TODO: Handle fgetpos failure */
    assert(fgetpos(input, &p) == 0);

    int c = fgetc(input);

    /* TODO: Handle fgetc failure */
    assert(c >= 0);

    /* TODO: Handle fsetpos failure */
    assert(fsetpos(input, &p) == 0);

    /* TODO: Handle fputc failure */
    assert(fputc(c + 1, input) != EOF);

    /* TODO: Handle fflush failure (Thank Kirilenko for this one) */
    assert(fflush(input) == 0);
}

Make sure you check return values...

Upvotes: 2

Remo.D
Remo.D

Reputation: 16512

I really suspect the problem is here:

fpos_t * pos;

You are declaring a pointer to a fpos_t which is fine but then, where are the infomation stored when you'll retrieve the pos?

It should be:

fpos_t pos; // No pointer
  ...
     fgetpos (input, &pos);
     fsetpos(input, &pos);  // You can only come back where you were!

Reading the (draft) standard, the only requirement for fpos_t is to be able to represent a position and a state for a FILE, it doesn't seem that there is a way to move the position around.

Note that the expression pos+1 move the pointer, does not affect the value it points to!

What you probably want is the old, dear ftell() and fseek() that will allow you to move around. Just remember to open the file with "rb+" and to flush() after your fputc().

When you'll have solved this basic problem you will note there is another problem with your approach: handling newlines! You most probably should restrict the range of characters you will apply your "increment" and stipulate that a follows z and A follows Z.

That said, is it a requirement to do it in-place?

Upvotes: 2

Grzegorz Piwowarek
Grzegorz Piwowarek

Reputation: 13783

The update mode('+') can be a little bit tricky to handle. Maybe You could just change approach and load the whole file into char array, iterate over it and then eventually write the whole thing to an emptied input file? No stream issues.

Upvotes: 1

md5
md5

Reputation: 23699

You have to be careful when dealing with files opened in update mode.

C11 (n1570), § 7.21.5.3 The fopen function

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.

So your reading might look something like :

int c;

while ((c = getc(input)) != EOF)
{
    fsetpos(/* ... */);
    putc(c + 1, input);
    fflush(input);
}

By the way, you will have problems with 'z' character.

Upvotes: 3

Related Questions