If I understand correctly, you are reading from a file into a buffer and substituting '@' for '!' when it occurs. Each of the comments above have provided you the reason, and a link to, why reading characters in a while loop and testing with feof is bad.
Putting the comments together, and cleaning up the logic a bit, the following shows a standard way to accomplish this task. I have left your code commented inline to make the changes more apparent. Take a look and drop a comment if you have questions.:
#include <stdio.h>
#define MAXC 255
int main (int argc, char **argv) {
    FILE *fp = NULL;
    int c;
    char input[MAXC];
    char buffer[MAXC];
    size_t idx = 0;
    if (argc < 2) {
        fprintf (stderr, "\n error: insufficient input, filename required.\n");
        return 1;
    }
    if (!(fp = fopen (argv[1], "r")))  {
        fprintf (stderr, "\n error: file open failed '%s' (%p)\n", argv[1], fp);
        return 1;
    }
    while ( (c = getc (fp)) != EOF ) 
    {
        input[idx] = c;
        if (c == '!') c = '@';
        buffer[idx++] = c;
        if (idx == MAXC) {  /* normally realloc if buffer is allocated */
            printf (" warning: maximum size of buffer reached  (%d char)\n", MAXC);
            break;
        }
        // {
        //     ungetc ('@', fp);
        // } else {
        //     ungetc (c, fp);
        // }
        // fgets (buffer, 255, fp);
        // fputs (buffer, stdout);
    }
    fclose (fp);
    input[idx] = buffer[idx] = 0;
    // fputs (buffer, stdout);
    printf ("\n characters in input & buffer are:\n\n");
    printf ("\n  original : %s\n  modified : %s\n\n", input, buffer);
    return 0;
}
Output
$ ./bin/whyfeofbad dat/qbfox2.txt
 characters in input & buffer are:
  original : The quick! brown fox jumps over the lazy! dog. He never says "Hi!", he just saunters up and jumps!
  modified : The quick@ brown fox jumps over the lazy@ dog. He never says "Hi@", he just saunters up and jumps@