-1

I wrote this code which reads every char of my text and puts it into my char array. My Problem is that the end of the file is not detected and so the fscanf() returns after the end of the text every time the last char until my array is filled. How can I prevent that? I am programming in C.

My Code:

int main() {
    char array[50][50];
    char buff;
    FILE *cola = fopen("C:/Users/danie/Desktop/cola.txt", "r");

    for (int i = 0; i < 50; i++) {
        for (int k = 0; k < 50; k++) {
            fscanf(cola, "%c", &buff);
            array[i][k] = buff;
        }
    }

    fclose(cola);

    for (int i = 0; i < 50; i++) {
        for (int k = 0; k < 50; k++) {
            printf("%c", array[i][k]);
        }
    }
    return 0;
}

Thank you for your help.

Here is a Screenshot of my Code

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Umut Savas
  • 219
  • 3
  • 19

3 Answers3

3

fscanf() returns the number of successful conversions. You should test the return value and also handle newline characters specifically:

#include <stdio.h>

int main(void) {
    char array[50][50];
    char buff;
    FILE *cola = fopen("C:/Users/danie/Desktop/cola.txt", "r");

    if (cola == NULL) {
        return 1;
    }
    for (int i = 0; i < 50; i++) {
        for (int k = 0; k < 50; k++) {
            if (fscanf(cola, "%c", &buff) != 1 || buff == '\n') {
                array[i][k] = '\0';
                break;
            }
            array[i][k] = buff;
        }
    }
    fclose(cola);

    for (int i = 0; i < 50; i++) {
        for (int k = 0; k < 50 && array[i][k] != '\0'; k++) {
            printf("%c", array[i][k]);
        }
        printf("\n");
    }
    return 0;
}

The code can be simplified if you use getc() instead of fscanf() to read bytes from the file:

#include <stdio.h>

int main(void) {
    char array[50][51];
    int c, i, k, n;
    FILE *cola = fopen("C:/Users/danie/Desktop/cola.txt", "r");

    if (cola == NULL) {
        return 1;
    }
    for (n = 0; n < 50; n++) {
        for (k = 0; k < 50; k++) {
            if ((c = getc(cola)) == EOF || c == '\n') {
                break;
            }
            array[n][k] = c;
        }
        array[n][k] = '\0';
        if (c == EOF && k == 0)
            break;
    }
    fclose(cola);

    for (i = 0; i < n; i++) {
        puts(array[i]);
    }
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
2

Replace:

for (int i = 0; i < 50; i++) {
    for (int k = 0; k < 50; k++) {
        fscanf(cola, "%c", &buff);
        array[i][k] = buff;
    }
}

with:

for (int i = 0; i < 50; i++) {
    for (int k = 0; k < 50; k++) {
        int c = getc(cola);
        if (c == EOF)
            break;
        array[i][k] = c;
    }
}

Since buff is then unused, don't define it. Note that the return type of getc() is an int, not just a char. Always check the I/O function for success/failure. In your original code, you don't even check whether the I/O operation succeeds, which makes detecting EOF impossible.

Note that this code makes a number of assumptions that may or may not be justifiable. For example, you assume each line in the file consists of 49 characters plus a newline; you also assume you'll never need to print the information as a 'string' (your existing code does not; it prints character by character, so it is 'safe').

You might want to describe the input as:

  • Read up to 50 lines with up to 49 characters plus a newline in each line, storing the result in the variable array with each line being a null-terminated string.

This is more resilient to common problems (short lines, long lines, not enough lines). The code for that might be:

enum { LINE_LEN = 50, NUM_LINES = 50 };
char array[NUM_LINES][LINE_LEN];
int i;
for (i = 0; i < LINE_LEN; i++)
{
    int c;
    int k;
    for (k = 0; k < LINE_LEN; k++)
    {
        c = getc(cola);
        if (c == EOF || c == '\n')
            break;
        if (k == LINE_LEN - 1)
        {
            /* Too long - gobble excess */
            while ((c = getc(cola)) != EOF && c != '\n')
                ;
            break;
        }
        array[i][k] = c;
    }
    array[i][k] = '\0';
    if (c == EOF)
        break;
}
int num_lines = i;   // You have num_lines lines of data in your array

I found one version of the Coca Cola™ ASCII art image at https://www.ascii-code.com/ascii-art/logos/coca-cola.php which looks similar to what you have in your images, but there are many other sources and variants:

         __                              ___   __        .ama     ,
      ,d888a                          ,d88888888888ba.  ,88"I)   d
     a88']8i                         a88".8"8)   `"8888:88  " _a8'
   .d8P' PP                        .d8P'.8  d)      "8:88:baad8P'
  ,d8P' ,ama,   .aa,  .ama.g ,mmm  d8P' 8  .8'        88):888P'
 ,d88' d8[ "8..a8"88 ,8I"88[ I88' d88   ]IaI"        d8[         
 a88' dP "bm8mP8'(8'.8I  8[      d88'    `"         .88          
,88I ]8'  .d'.8     88' ,8' I[  ,88P ,ama    ,ama,  d8[  .ama.g
[88' I8, .d' ]8,  ,88B ,d8 aI   (88',88"8)  d8[ "8. 88 ,8I"88[
]88  `888P'  `8888" "88P"8m"    I88 88[ 8[ dP "bm8m88[.8I  8[
]88,          _,,aaaaaa,_       I88 8"  8 ]P'  .d' 88 88' ,8' I[
`888a,.  ,aadd88888888888bma.   )88,  ,]I I8, .d' )88a8B ,d8 aI
  "888888PP"'        `8""""""8   "888PP'  `888P'  `88P"88P"8m"

This file's longest line is the first at 67 characters plus newline; the shortest is 61 characters plus newline. The file only has 13 lines and 845 characters (LF line endings) in total. Thus, your program is ill-equipped to deal with this particular data file. It looks for 2,500 characters, and won't get them.

My complete test code was rigged to read from standard input, rather than a fixed file name.

#include <stdio.h>

int main(void)
{
    FILE *cola = stdin;

    enum { LINE_LEN = 80, NUM_LINES = 50 };
    char array[NUM_LINES][LINE_LEN];
    int i;      // Need value of i after loop
    for (i = 0; i < NUM_LINES; i++)
    {
        int c;  // Need value of c after loop
        int k;
        for (k = 0; k < LINE_LEN; k++)
        {
            c = getc(cola);
            if (c == EOF || c == '\n')
                break;
            if (k == LINE_LEN - 1)
            {
                /* Too long - gobble excess */
                while ((c = getc(cola)) != EOF && c != '\n')
                    ;
                break;
            }
            array[i][k] = c;
        }
        array[i][k] = '\0';
        if (c == EOF)
            break;
    }
    int num_lines = i;   // You have num_lines lines of data in your array

    for (i = 0; i < num_lines; i++)
        puts(array[i]);

    return 0;
}

I tested it on the data file shown, with an empty line at the end, and with a couple of lines containing more than 79 characters after the blank line. It handled all those special cases correctly. Note that handling user input is hard; handling perverse user input is harder. The code is less compact. You could change the rules and then change the code to match. I'm not sure this is the most minimal way to code this; it does work, however. It might be better to have a function to handle the inner input loop; the outer loop could test the return value from that function. This would cut down on the special case handling.

#include <assert.h>
#include <limits.h>
#include <stdio.h>

static int read_line(FILE *fp, size_t buflen, char *buffer)
{
    assert(buflen < INT_MAX);
    int c;      // Need value of c after loop
    size_t k;   // Need value of k after loop
    for (k = 0; k < buflen; k++)
    {
        if ((c = getc(fp)) == EOF || c == '\n')
            break;
        if (k == buflen - 1)
        {
            /* Too long - gobble excess */
            while ((c = getc(fp)) != EOF && c != '\n')
                ;
            break;
        }
        buffer[k] = c;
    }
    buffer[k] = '\0';
    return (k == 0 && c == EOF) ? EOF : (int)k;
}

int main(void)
{
    enum { LINE_LEN = 80, NUM_LINES = 50 };
    char array[NUM_LINES][LINE_LEN];
    int i;
    for (i = 0; i < NUM_LINES; i++)
    {
        if (read_line(stdin, LINE_LEN, array[i]) == EOF)
            break;
    }
    int num_lines = i;

    for (i = 0; i < num_lines; i++)
        puts(array[i]);

    return 0;
}

This produces the same output from the same input as the previous version.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • This change alone does not fix the problem, the printing loop will access an uninitialized portion of the array, invoking undefined behavior. – chqrlie Sep 16 '17 at 17:49
  • @chqrlie — You're probably right. I only addressed the headline question (about not detecting EOF); I didn't look at any of the rest of the code. I didn't even compile anything. I also don't have overflow protection in place, etc. – Jonathan Leffler Sep 16 '17 at 17:51
  • Impressive effort and good insight, everyone else missed the line length issue. I guess the `assert(buflen < INT_MAX);` could be changed to `assert(buflen <= INT_MAX);` – chqrlie Sep 16 '17 at 22:23
  • @chqrlie: Yes, you could use `assert(buflen <= INT_MAX);`, but I'd worry about any use of the code where that one byte made the difference. – Jonathan Leffler Sep 16 '17 at 22:28
-1
int main() {
//char array[50][50];
char buff;
int t;
FILE *cola = fopen("C:/Users/danie/Desktop/cola.txt", "r");

if (cola == NULL)
{
    printf("Cannot open file \n");
    exit(0);
}
while (1) {
    t = fgetc(cola);
    if (t == EOF)
        break;
    buff = t;
    printf("%c", buff);
}


fclose(cola);

return 0;
}
Aashish Kumar
  • 2,771
  • 3
  • 28
  • 43
  • 1
    You should test for `EOF` **before** printing the character with `printf("%c", buff);`. Furthermore, since you use `fgetc(cola)` to read from the stream, you **must** define `buff` with type `int` for `EOF` to be properly detected, and you might as well use `putchar(buff)` for the output. – chqrlie Sep 16 '17 at 17:52
  • @chqrlie `fgetc()` returns an int but when we store the return value in char datatype then it automatically stores the character equivalent of return ASCII. i.e implicitly typecasted. – Aashish Kumar Sep 16 '17 at 18:01
  • 1
    The trouble is that if you save the value from `getc()` and its relatives in a `char` before you test it, you throw away the EOF indication. If plain `char` is a signed type, you will misidentify a valid character as EOF; if it is an unsigned type, you will never detect EOF. Both are bad. – Jonathan Leffler Sep 16 '17 at 18:08
  • @JonathanLeffler I just test this code with signed type and its work fine and in case of the unsigned type it never detects EOF then how above while loop terminates while all ASCII value has unsigned type. – Aashish Kumar Sep 16 '17 at 18:33
  • 1
    Testing is not proving. Storing the return value of `getc()` into a `char` variable is plain wrong. It prevents proper testing of end of file, and worse even: it has implementation defined behavior if the return value is outside the range of type `char`, which may happen if you read non ASCII files. Just use `int` as explained in chapter **1.5.1 File Copying** of the original book *The C programming language* by Kernighan & Richie. – chqrlie Sep 16 '17 at 19:39
  • See [`while ((c = getc(file)) != EOF)` loop won't stop executing](http://stackoverflow.com/questions/13694394/while-c-getcfile-eof-loop-wont-stop-executing/13694450#13694450) as one of a number of questions addressing this issue. Your test results suggest that either you're not using a single-bytes code set (SBCS) such as ISO 8859-15 or CP1252, or you didn't try the critical character (ÿ in both — Unicode U+00FF, LATIN SMALL LETTER Y WITH DIAERESIS). The byte 0xFF cannot appear in valid UTF-8. – Jonathan Leffler Sep 16 '17 at 19:56
  • Thanks for providing appropriate information. So, I could understand that what will be the actual issue. – Aashish Kumar Sep 17 '17 at 03:54