A function call to fopen
or open
must be matched with a call to fclose
or close
,
respectively.
Why is this an issue?
The standard C library provides fopen
and the system call open
to open and possibly create files. Each call to one of
these functions must be matched with a respective call to fclose
or close
.
Failing to close files that have been opened may lead to using up all of the OS’s file handles.
What is the potential impact?
If a program does not properly close or release file handles after using them, it will leak resources. In that case, the program will continue to
hold onto file handles even when they are no longer needed, eventually exhausting the available file handles.
If a program has run out of file handles and tries to open yet another file, the file opening operation will fail. This can result in errors or
unexpected behavior in the program.
The program may not be able to read or write to files anymore, which can cause data loss, corruption, or incomplete operations. In some cases, when
a program runs out of file handles, it may crash or hang indefinitely. This can happen if the program does not handle the error condition properly or
if it enters an infinite loop trying to open files, for instance. In the worst case, a resource leak can lock up everything that runs on the
machine.
How to fix it
Make sure that each call to fopen
and open
has a matching call to fclose
and close
,
respectively.
Code examples
Noncompliant code example
#include <stdio.h>
#include <stdlib.h>
int process_file(int print) {
FILE *f = fopen("example.txt", "r");
if (!f) {
perror("fopen() failed");
return 1;
}
if (print) {
char buffer[256];
while (fgets(buffer, 256, f)) {
printf("%s", buffer);
}
}
return 0; // Noncompliant: file `f` has not been closed
}
Compliant solution
#include <stdio.h>
#include <stdlib.h>
int process_file(int print) {
FILE *f = fopen("example.txt", "r");
if (!f) {
perror("fopen() failed");
return 1;
}
if (print) {
char buffer[256];
while (fgets(buffer, 256, f)) {
printf("%s", buffer);
}
}
fclose(f);
return 0; // Compliant: file `f` has been closed
}
Going the extra mile
Using C++'s RAII idiom can mitigate unmatched calls to fopen
and open
.
Following this idiom, one would create a class that manages the underlying file by opening it when the object is constructed and closing it when
the object is destroyed, effectively using a constructor-destructor pair as a "do-undo"-mechanism.
An exemplary class that manages a pointer to a file is shown in what follows.
#include <cstdio>
#include <fstream>
#include <string>
#include <utility>
// Although `std::fstream` should be preferred, if available, a file stream
// managed by this `File` class cannot suffer from "double-close" issues.
class File {
FILE *f;
public:
// Opens file stream on construction.
File(std::string const &path, std::string const &modes)
: f(fopen(path.c_str(), modes.c_str())) {
if (!f) {
throw std::ios_base::failure("fopen() failed");
}
}
// Will close the file stream upon destruction.
~File() {
// Here we are fine with `std::terminate` being called here in case `close`
// throws and exception.
close();
}
// Allow only one owner of a file, disallow copy operations.
File(const File &other) = delete;
File &operator=(const File &other) = delete;
// Moving a file to a different scope shall be allowed.
File(File &&other) : f(std::exchange(other.f, nullptr)) {}
File &operator=(File &&other) {
if (this != &other) {
// In case of non-self-assignment, close the currently managed file and
// "steal" the other's file.
close();
f = std::exchange(other.f, nullptr);
}
return *this;
}
// Allow file to be closed explicitly.
void close() {
if (f != nullptr && fclose(std::exchange(f, nullptr)) == EOF) {
throw std::ios_base::failure("fclose() failed");
}
}
// Allow access to underlying file via `f`.
FILE *handle() { return f; }
// Release `f`, i.e., stop managing it.
FILE *release() { return std::exchange(f, nullptr); }
};
void file_user() {
File fh{"example.txt", "r"};
FILE *f = fh.handle();
// Use `f` for the desired file operation(s).
//
// The file stream managed by `fh` will be automatically closed when `fh` goes
// out of scope at the end of this function.
}
With the design shown above, calls to fopen
will be automatically matched with a corresponding call to fclose
by default.
The associated file will be automatically closed when the File
typed file handle object goes out of scope and its destructor is called.
However, it is still possible to leak a resource, if File
's function member File::release
is used inappropriately. If this
is a concern, this function member should be removed.
If falling back to low-level file operations is not necessary, one should prefer std::fstream
.
Resources
Standards
Related rules
- S3588 ensures that
FILE*
typed variables are not accessed after the associated file has been closed