-2

When testing the fclose function, the printf("File \"file2.bin\" inexistent!"); line is not reached. If the fclose(p_file2) line is removed, the message is shown.

#include <stdio.h>

int main()
{
    printf("Hello files");
    FILE *p_file1, *p_file2;
    p_file1 = fopen("file1.bin", "w+b");
    p_file2 = fopen("file2.bin", "r+b");
    if (p_file2 == NULL)
        printf("File \"file2.bin\" inexistent!");
    
    fclose(p_file1);
    fclose(p_file2);

    return 0;
}

Why does that happen?

Oka
  • 23,367
  • 6
  • 42
  • 53
Dalton Cézane
  • 3,672
  • 2
  • 35
  • 60
  • 3
    What does `fclose(NULL)` do? Why do you think the line `printf(...)` is not reached? The only evidence you have is that the string "File \"file2.bin\" inexistent!" does not appear in the output, but you are incorrect to conclude this means that the line of code was not executed. – William Pursell May 11 '23 at 12:55
  • What compiler? What platform? – Adrian Mole May 11 '23 at 12:55
  • 1
    If you want to determine whether the line `printf("File \"file2.bin\" inexistent!");` is reached or not, you can run your program line by line in a [debugger](https://stackoverflow.com/q/25385173/12149471). If you do that, you will notice that the line is reached. See the answer by @WilliamPursell for an explanation of how it is possible that you do not the see output, although the line is reached. – Andreas Wenzel May 11 '23 at 13:40
  • 1
    Also note that a return of NULL from `fopen` does not necessarily mean that the file does not exist. There are many reasons that `fopen` may return NULL, and it can confuse the user if you blindly assume a reason. Let the system tell you why `fopen` failed. (eg: `perror(path)`, which also writes the error message to the correct stream. – William Pursell May 11 '23 at 13:48
  • @WilliamPursell: In contrast to POSIX, ISO C does not require that `fopen` sets `errno` on failure. Therefore, what you recommend is not guaranteed to work. However, I believe it will work on most common platforms. – Andreas Wenzel May 11 '23 at 14:05
  • Thanks for clarifying what should have been obvious to me. In a hurry, I ignored the cause (null reference) and focused only on the problem (not displaying the message). Anyway, it made sense to me that the message would be displayed, since it precedes the function call by passing a null reference. That is, the error when using fclose only happens afterwards. – Dalton Cézane May 11 '23 at 14:54

3 Answers3

4

2 primary issues here. The first is that fclose(NULL) leads to undefined behavior, so there is really no point is speculating about the other point, but it is clearly a source of confusion. printf does not always write any data to the output stream. Instead, the data is buffered and the write may be deferred to a later call to printf or until the program exits (or a call to fwrite or fflush or other conditions). Since your program invoked fclose(NULL), bad things happen and the write that should have been deferred never happens, so you don't see the message.

Try adding a call to fflush immediately after the printf.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • 2
    Adding a newline (`\n') at the end of the format string would also help, typically standard output is line-buffered. – G. Sliepen May 11 '23 at 13:17
  • 3
    `fsync()` is not the right tool for the job, and it is not easily used in this case anyway because it requires a file descriptor, not a stream. Perhaps you meant `fflush()`? – John Bollinger May 11 '23 at 13:36
  • You may want to clarify that it is necessary to write `fflush( stdout );`, because otherwise, OP may attempt to flush one of the other streams instead. – Andreas Wenzel May 11 '23 at 13:52
  • 1
    @AndreasWenzel Yes, my comment was wong and actually I can't even find that `fclose(NULL)` is UB. William, could you please cite a reference that says that this is UB? – Konrad Rudolph May 11 '23 at 13:52
  • 1
    @KonradRudolph: I believe the behavior is undefined according to the general rule of [§7.1.4 ¶1 of the ISO C11 standard](http://port70.net/~nsz/c/c11/n1570.html#7.1.4p1). – Andreas Wenzel May 11 '23 at 13:58
  • @AndreasWenzel Yup, that covers it. Funny enough I just voiced that suspicion in a a comment under another answer but couldn't find the wording. – Konrad Rudolph May 11 '23 at 13:59
3
  1. Passing a NULL pointer to fclose() invokes undefined behaviour.¹

As the program exits on fopen() failure, we could have a function like so:

static void open_sesame (const char *restrict stream, const char *restrict mode) 
{
    if (!fopen (stream, mode)) {
        perror ("fopen():");
        exit (EXIT_FAILURE);
    }
}

Now your code simplifies to:

FILE *fp1 = open_sesame ("file1.bin", "w+b");
FILE *fp2 = open_sesame ("file2.bin", "r+b");
...
fclose (fp1);
fclose (fp2);

  1. The data buffered by printf() is flushed when fflush (stdout) is called, or when the program terminates. If stdout is line-buffered (which is usually the case when the stream is connected to a terminal/console), then the stream will also be flushed when a newline character is encountered. But since the program invokes undefined behaviour, no assumption about the continuation of the execution of the program can be made.

Add a newline in the call to printf(), or call fflush (stdout);.


Footnote:

The standard doesn't define the behaviour, so this rule applies:

Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, or a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) or a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined. — 7.1.4p1 C18 standard.

Harith
  • 4,663
  • 1
  • 5
  • 20
  • Do you have a reference for `fclose(NULL)` being UB? I don't have the current C standard document handy, but older versions don't say that as far as I can see (unless I am overlooking a wording somewhere that all library functions expect valid pointers). – Konrad Rudolph May 11 '23 at 13:57
  • @KonradRudolph I've added the relevant rule. – Harith May 11 '23 at 16:42
1

Only call fclose on valid FILE pointers that have actually been returned by fopen.

Basically you want this:

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

int main()
{
  printf("Hello files");
  FILE* p_file1, * p_file2;
  p_file1 = fopen("file1.bin", "w+b");
  if (p_file1 == NULL)
  {
    printf("Can't open file.\n");  // add \n
    exit(1);
  }

  p_file2 = fopen("file2.bin", "r+b");
  if (p_file2 == NULL)
  {
    fclose(p_file1); // this can be ommited because files are closed automatically upon exit
    printf("Can't open file.\n");   // add \n
    exit(1);
  }
  
  // here we know for sure that both p_file1 and p_file1 are valid
  fclose(p_file1);
  fclose(p_file2);

  return 0;
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115