Cstdio
<cstdio> exposes C stdio (printf, fopen, snprintf, fread) in namespace std: formatted and binary file I/O, its pitfalls, and modern C++ alternatives.
<cstdio>since C++98The C++ header that declares the C standard I/O library (FILE, printf, fopen, fread, and related functions) in namespace std, with the same semantics as C's <stdio.h>.
Overview
<cstdio> is the C++ wrapper around C's <stdio.h>. It guarantees the declarations exist in std::; whether they are also in the global namespace is implementation-defined, so portable code should write std::printf and std::fopen. <stdio.h> remains valid in C++ and, in practice, puts names in the global namespace, with std:: availability not guaranteed by older standards.
The library is built around the opaque std::FILE stream type. It provides:
- Formatted output:
printf,fprintf,sprintf,snprintf, and thev*variants takingva_list. - Formatted input:
scanf,fscanf,sscanf. - Character and line I/O:
fgetc,fputc,fgets,fputs,puts,ungetc. - Binary I/O:
fread,fwrite,fseek,ftell,fgetpos,fsetpos,rewind. - Stream management:
fopen,freopen,fclose,fflush,setvbuf,tmpfile. - File operations:
remove,rename,tmpnam. - Error state:
feof,ferror,clearerr,perror. - Standard streams:
stdin,stdout,stderr, plusEOF,BUFSIZ,FILENAME_MAX,SEEK_SET/CUR/END.
Version notes:
std::snprintf,std::vsnprintf,std::vsscanf, and the%zu/%lldlength modifiers entered C++ in C++11 (from C99).std::getswas deprecated in C++11 and removed in C++14. Never use it.- The exclusive mode flag
"x"forfopen(fail if the file exists) is available since C++17 (from C11). std::print/std::println(C++23,<print>) write to aFILE*and are the modern replacement for mostfprintfuse.
<cstdio> is not constexpr-friendly and not thread-neutral: each FILE has an internal lock, so individual calls are thread-safe, but sequences of calls interleave unless you hold the lock with POSIX flockfile.
Syntax
#include <cstdio>
std::FILE* std::fopen(const char* filename, const char* mode);
int std::fclose(std::FILE* stream);
std::size_t std::fread(void* buf, std::size_t size, std::size_t count, std::FILE* stream);
std::size_t std::fwrite(const void* buf, std::size_t size, std::size_t count, std::FILE* stream);
int std::fprintf(std::FILE* stream, const char* format, ...);
int std::snprintf(char* buf, std::size_t n, const char* format, ...); // since C++11
char* std::fgets(char* buf, int n, std::FILE* stream);
int std::fseek(std::FILE* stream, long offset, int origin);Mode strings: "r", "w", "a", with optional + (update) and b (binary). "wx" (C++17) fails if the file exists. Failure returns nullptr with errno set.
Examples
RAII wrapper with unique_ptr
FILE* is a raw resource; wrap it so every path closes it.
#include <cstdio>
#include <memory> // C++11 for unique_ptr
#include <vector>
#include <string>
struct FileCloser {
void operator()(std::FILE* f) const noexcept { if (f) std::fclose(f); }
};
using FilePtr = std::unique_ptr<std::FILE, FileCloser>; // C++11
FilePtr open_file(const char* path, const char* mode) {
return FilePtr{std::fopen(path, mode)};
}
std::vector<unsigned char> read_all(const char* path) {
auto f = open_file(path, "rb");
if (!f) return {};
std::fseek(f.get(), 0, SEEK_END);
long len = std::ftell(f.get());
std::rewind(f.get());
if (len < 0) return {};
std::vector<unsigned char> data(static_cast<std::size_t>(len));
std::size_t got = std::fread(data.data(), 1, data.size(), f.get());
data.resize(got); // short read is possible
return data;
}A stateless deleter type keeps sizeof(FilePtr) == sizeof(FILE*); a std::function deleter or shared_ptr<FILE> works but costs extra.
Safe formatting with snprintf
#include <cstdio>
#include <string>
std::string format_id(const char* prefix, unsigned long id) {
// C++11: snprintf returns the length that WOULD have been written
int n = std::snprintf(nullptr, 0, "%s-%08lx", prefix, id);
if (n < 0) return {};
std::string out(static_cast<std::size_t>(n), '\0');
// buffer needs n+1 for the terminator; std::string guarantees it since C++11
std::snprintf(out.data(), out.size() + 1, "%s-%08lx", prefix, id); // data() non-const since C++17
return out;
}The two-pass idiom (measure, then write) avoids truncation. out.data() returning char* requires C++17; on C++11/14 use &out[0].
Line-oriented reading with proper error checks
#include <cstdio>
#include <cstring>
#include <string>
bool for_each_line(const char* path, void (*fn)(const std::string&)) {
std::FILE* f = std::fopen(path, "r");
if (!f) { std::perror(path); return false; }
char buf[512];
std::string line;
while (std::fgets(buf, sizeof buf, f)) {
line += buf;
if (line.back() == '\n') { // full line assembled
line.pop_back();
fn(line);
line.clear();
}
}
if (!line.empty()) fn(line); // last line without newline
bool ok = !std::ferror(f);
std::fclose(f);
return ok;
}Best Practices
- Prefer streams or
std::format/std::printfor new code. Streams are type-safe and extensible. Since C++20,std::formatgives type-checked format strings; since C++23,std::print(FILE*, ...)writes them directly to stdio streams. - Use
<cstdio>where it fits: interop with C libraries,FILE*handed to you by an API, binary I/O with predictable performance,fread/fwriteinto preallocated buffers,setvbuftuning, andstderrlogging in crash paths. - Always RAII-wrap
FILE*. An earlyreturnor exception betweenfopenandfcloseleaks the handle. If you wrap it in a class, delete copy operations (or make it move-only); a copyable wrapper double-closes. - Check every return value:
fopenfornullptr,fread/fwritefor short counts, andfcloseon written files, because buffered data is flushed at close and failures often surface only there. - Use
snprintf, neversprintf. Pair it with compiler format checking (__attribute__((format(printf, ...)))on GCC/Clang) for your own variadic wrappers. - Disambiguate with
ferror/feofafter a short read:feofmeans end of input,ferrormeans an I/O failure. - Mixing
iostreamand stdio: withsync_with_stdio(true)(the default) output interleaves correctly, at a speed cost. Callstd::ios::sync_with_stdio(false)only if you never touchstdout/stdinthrough stdio.
Common Pitfalls
- Format/argument mismatch is undefined behavior.
printf("%d", 3.0)or%swith astd::stringcompiles (with at most a warning) and misbehaves. Use%zuforsize_t(C++11),%lldforlong long, and.c_str()for strings. Passing non-trivial class types through...is conditionally supported at best. - Narrow-type promotion:
floatpromotes todoubleand small integers tointin variadic calls;scanfreceives pointers, so%finto adouble*is wrong (needs%lf). scanf("%s", buf)has no bounds checking. Always specify a width (%31s) or preferfgetsplus parsing (std::from_charssince C++17).getsis gone. It was removed in C++14; code that still compiles it is relying on a non-conforming mode.- Macro side effects:
getcandputcmay be macros that evaluate the stream argument more than once, sogetc(*files++)is a bug. Usefgetc/fputcwhen the argument has side effects. - Mixed reads and writes on
"r+"/"w+"streams require an interveningfflushor a positioning call (fseek,rewind); otherwise behavior is undefined. - Text vs. binary mode: on Windows,
"r"translates\r\n, which corrupts binary data and makesftelloffsets unreliable. Use"rb"/"wb". ftellreturnslong, which is 32-bit on Windows and limits files to 2 GB; use_ftelli64or POSIXfseeko/ftellofor large files, orstd::filesystem::file_size(C++17) for size queries.tmpnamis racy (check-then-create). Usetmpfile()or POSIXmkstemp.- Use after
fclose, orfclosetwice, is undefined behavior. AFILE*copied into two owners invites exactly this. - Not exception-aware: stdio reports errors through return codes and
errno, and never throws. Callbacks and destructors that call into it must propagate failures deliberately.
See Also
std::unique_ptrwith custom deleters (C++11) for owningFILE*std::format(C++20) andstd::print(C++23) as type-safeprintfreplacementsstd::filesystem(C++17) forremove,rename, and size/metadata operations beyond what<cstdio>offers<fstream>and<iostream>for stream-based I/O<cstdarg>for writing your own variadic functions that forward tovfprintf