Recent from talks
Nothing was collected or created yet.
C file input/output
View on Wikipedia
| C standard library (libc) |
|---|
| General topics |
| Miscellaneous headers |
The C programming language provides many standard library functions for file input and output. These functions make up the bulk of the C standard library header <stdio.h>.[1] The functionality descends from a "portable I/O package" written by Mike Lesk at Bell Labs in the early 1970s,[2] and officially became part of the Unix operating system in Version 7.[3]
The I/O functionality of C is fairly low-level by modern standards; C abstracts all file operations into operations on streams of bytes, which may be "input streams" or "output streams". Unlike some earlier programming languages, C has no direct support for random-access data files; to read from a record in the middle of a file, the programmer must create a stream, seek to the middle of the file, and then read bytes in sequence from the stream.
The stream model of file I/O was popularized by Unix, which was developed concurrently with the C programming language itself. The vast majority of modern operating systems have inherited streams from Unix, and many languages in the C programming language family have inherited C's file I/O interface with few if any changes (for example, PHP).
Overview
[edit]This library uses what are called streams to operate with physical devices such as keyboards, printers, terminals or with any other type of files supported by the system. Streams are an abstraction to interact with these in a uniform way. All streams have similar properties independent of the individual characteristics of the physical media they are associated with.[4]
Functions
[edit]Most of the C file input/output functions are defined in <stdio.h> (or in the C++ header <cstdio>, which contains the standard C functionality but in the std namespace).
| Byte character |
Wide character |
Description | |
|---|---|---|---|
| File access | fopen | Opens a file (with a non-Unicode filename on Windows and possible UTF-8 filename on Linux) | |
| popen | opens a process by creating a pipe, forking, and invoking the shell | ||
| freopen | Opens a different file with an existing stream | ||
| fflush | Synchronizes an output stream with the actual file | ||
| fclose | Closes a file | ||
| pclose | closes a stream | ||
| setbuf | Sets the buffer for a file stream | ||
| setvbuf | Sets the buffer and its size for a file stream | ||
| fwide | Switches a file stream between wide-character I/O and narrow-character I/O | ||
| Direct input/output |
fread | Reads from a file | |
| fwrite | Writes to a file | ||
| Unformatted input/output |
fgetc getc |
fgetwc getwc |
Reads a byte/wchar_t from a file stream |
| fgets | fgetws | Reads a byte/wchar_t line from a file stream | |
| fputc putc |
fputwc putwc |
Writes a byte/wchar_t to a file stream | |
| fputs | fputws | Writes a byte/wchar_t string to a file stream | |
| getchar | getwchar | Reads a byte/wchar_t from stdin | |
| — | Reads a byte string from stdin until a newline or end of file is encountered (deprecated in C99, removed from C11) | ||
| putchar | putwchar | Writes a byte/wchar_t to stdout | |
| puts | — | Writes a byte string to stdout | |
| ungetc | ungetwc | Puts a byte/wchar_t back into a file stream | |
| Formatted input/output |
scanf fscanf sscanf |
wscanf fwscanf swscanf |
Reads formatted byte/wchar_t input from stdin, a file stream or a buffer |
| vscanf vfscanf vsscanf |
vwscanf vfwscanf vswscanf |
Reads formatted input byte/wchar_t from stdin, a file stream or a buffer using variable argument list | |
| printf fprintf sprintf snprintf |
wprintf fwprintf swprintf |
Prints formatted byte/wchar_t output to stdout, a file stream or a buffer | |
| vprintf vfprintf vsprintf vsnprintf |
vwprintf vfwprintf vswprintf |
Prints formatted byte/wchar_t output to stdout, a file stream, or a buffer using variable argument list | |
| perror | — | Writes a description of the current error to stderr | |
| File positioning | ftell ftello |
Returns the current file position indicator | |
| fseek fseeko |
Moves the file position indicator to a specific location in a file | ||
| fgetpos | Gets the file position indicator | ||
| fsetpos | Moves the file position indicator to a specific location in a file | ||
| rewind | Moves the file position indicator to the beginning in a file | ||
| Error handling |
clearerr | Clears errors | |
| feof | Checks for the end-of-file | ||
| ferror | Checks for a file error | ||
| Operations on files |
remove | Erases a file | |
| rename | Renames a file | ||
| tmpfile | Returns a pointer to a temporary file | ||
| tmpnam | Returns a unique filename | ||
Constants
[edit]Constants defined in the <stdio.h> header include:
| Name | Notes |
|---|---|
| EOF | A negative integer of type int used to indicate end-of-file conditions |
| BUFSIZ | An integer which is the size of the buffer used by the setbuf() function |
| FILENAME_MAX | The size of a char array which is large enough to store the name of any file that can be opened |
| FOPEN_MAX | The number of files that may be open simultaneously; will be at least eight |
| _IOFBF | An abbreviation for "input/output fully buffered"; it is an integer which may be passed to the setvbuf() function to request block buffered input and output for an open stream |
| _IOLBF | An abbreviation for "input/output line buffered"; it is an integer which may be passed to the setvbuf() function to request line buffered input and output for an open stream |
| _IONBF | An abbreviation for "input/output not buffered"; it is an integer which may be passed to the setvbuf() function to request unbuffered input and output for an open stream |
| L_tmpnam | The size of a char array which is large enough to store a temporary filename generated by the tmpnam() function |
| NULL | A macro expanding to the null pointer constant; that is, a constant representing a pointer value which is guaranteed not to be a valid address of an object in memory |
| SEEK_CUR | An integer which may be passed to the fseek() function to request positioning relative to the current file position |
| SEEK_END | An integer which may be passed to the fseek() function to request positioning relative to the end of the file |
| SEEK_SET | An integer which may be passed to the fseek() function to request positioning relative to the beginning of the file |
| TMP_MAX | The maximum number of unique filenames generable by the tmpnam() function; will be at least 25 |
Variables
[edit]
Variables defined in the <stdio.h> header include:
| Name | Notes |
|---|---|
| stdin | A pointer to a FILE which refers to the standard input stream, usually a keyboard. |
| stdout | A pointer to a FILE which refers to the standard output stream, usually a display terminal. |
| stderr | A pointer to a FILE which refers to the standard error stream, often a display terminal. |
Member types
[edit]Data types defined in the <stdio.h> header include:
- FILE – also known as a file handle or a FILE pointer, this is an opaque pointer containing the information about a file or text stream needed to perform input or output operations on it, including:
- platform-specific identifier of the associated I/O device, such as a file descriptor
- the buffer
- stream orientation indicator (unset, narrow, or wide)
- stream buffering state indicator (unbuffered, line buffered, fully buffered)
- I/O mode indicator (input stream, output stream, or update stream)
- binary/text mode indicator
- end-of-file indicator
- error indicator
- the current stream position and multibyte conversion state (an object of type
mbstate_t) - reentrant lock (required as of C11)
fpos_t– a non-array type capable of uniquely identifying the position of every byte in a file and every conversion state that can occur in all supported multibyte character encodingssize_t– an unsigned integer type which is the type of the result of the sizeof operator.
Extensions
[edit]The POSIX standard defines several extensions to <stdio.h> in its Base Definitions, among which are a readline function that allocates memory, the fileno and fdopen functions that establish the link between FILE objects and file descriptors, and a group of functions for creating FILE objects that refer to in-memory buffers.[5]
Example
[edit]The following C program opens a binary file called myfile.txt, reads five bytes from it, and then closes the file.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char FILE_NAME[] = "myfile.txt";
const int BUFFER_SIZE = 5;
char buffer[BUFFER_SIZE];
size_t len;
FILE* fp = fopen("myfile.txt", "rb");
if (!fp) {
fprintf(stderr, "Failed to open file \"%s\"!", FILE_NAME);
return EXIT_FAILURE;
}
if ((len = fread(buffer, 1, 5, fp)) < 0) {
fclose(fp);
fprintf(stderr, "An error occurred while reading file \"%s\".\n", FILE_NAME);
return EXIT_FAILURE;
}
fclose(fp);
printf("The bytes read were: ");
for (int i = 0; i < BUFFER_SIZE; ++i) {
printf("%02X ", buffer[i]);
}
putchar('\n');
return EXIT_SUCCESS;
}
Alternatives to stdio
[edit]Several alternatives to <stdio.h> have been developed. Among these are C++ I/O headers <iostream> and <print>, part of the C++ Standard Library. ISO C++ still requires the <stdio.h> functionality, and it is found under header <cstdio>.
Other alternatives include the Sfio[6] (A Safe/Fast I/O Library) library from AT&T Bell Laboratories. This library, introduced in 1991, aimed to avoid inconsistencies, unsafe practices and inefficiencies in the design of <stdio.h>. Among its features is the possibility to insert callback functions into a stream to customize the handling of data read from or written to the stream.[7] It was released to the outside world in 1997, and the last release was 1 February 2005.[8]
See also
[edit]References
[edit]- ^ ISO/IEC 9899:1999 specification. p. 274, § 7.19.
- ^ Kernighan, Brian; Pike, Rob (1984). The UNIX Programming Environment. Englewood Cliffs: Prentice Hall. p. 200. Bibcode:1984upe..book.....K.
- ^ McIlroy, M. D. (1987). A Research Unix reader: annotated excerpts from the Programmer's Manual, 1971–1986 (PDF) (Technical report). CSTR. Bell Labs. 139.
- ^ "(stdio.h) - C++ Reference". C++. Retrieved 25 July 2021.
- ^ – Base Definitions Reference, The Single UNIX Specification, Version 5 from The Open Group
- ^ "Sfio: A Safe/Fast I/O Library". Archived from the original on 11 February 2006. Retrieved 16 March 2021.
{{cite web}}: CS1 maint: bot: original URL status unknown (link) - ^ Korn, David G.; Vo, Kiem-Phong (1991). SFIO: Safe/Fast String/File IO. Proc. Summer USENIX Conf. CiteSeerX 10.1.1.51.6574.
- ^ Fowler, Glenn S.; Korn, David G.; Vo, Kiem-Phong (2000). Extended Formatting with Sfio. Proc. Summer USENIX Conf.
External links
[edit]
Media related to C file input/output at Wikimedia Commons
C file input/output
View on Grokipedia<stdio.h> header, revolve around the FILE type, which represents an open file or stream, and include functions for opening, closing, reading, writing, positioning, and error management.[1] The system supports both text and binary modes, with text mode handling newline translations and binary mode preserving raw data, ensuring compatibility across diverse host environments.[1]
Key operations begin with file access via fopen, which associates a stream with a named file using specified modes such as "r" for reading, "w" for writing, or "a" for appending, returning a FILE pointer on success or NULL on failure.[1] Binary data transfer uses fread to read a specified number of items into a buffer and fwrite to write from a buffer to the file, both returning the number of items successfully processed.[1] For formatted I/O, fprintf outputs data according to a format string to a stream, while fscanf inputs and parses data from a stream using matching format specifiers, both facilitating structured text handling akin to console I/O.[1]
Character-level I/O functions like fgetc and fputc manage single characters, with line-oriented variants fgets and fputs for strings, and macros getc and putc offering potentially optimized implementations.[1] File positioning is controlled by fseek to relocate the stream's position indicator, ftell to query it, and rewind to reset to the beginning, supporting random access.[1] Error and state management includes fflush to synchronize buffers, clearerr to reset indicators, feof and ferror to test conditions, and fclose to release resources upon completion.[1] Additionally, predefined standard streams—stdin for input, stdout for output, and stderr for diagnostics—enable console-like I/O without explicit file opening.[2] Auxiliary functions such as remove for deletion, rename for renaming, tmpfile for temporary files, and freopen for stream redirection further extend capabilities.[1] This framework, specified in ISO/IEC 9899 (C11 and later), promotes portability while buffering data for efficiency.[1]
Fundamentals
Core Concepts
The standard input/output library in C, defined in the<stdio.h> header, provides the core functions and types for performing file input and output operations. To utilize these facilities, programs must include the header using the preprocessor directive #include <stdio.h>.[3] This header declares types, macros, and functions that enable portable handling of input and output across diverse computing environments.[4]
File input/output in C treats files as streams of characters, supporting buffered, byte-oriented operations that read from or write to these streams in a sequential manner. Buffering is inherent to the library's design, where data is temporarily stored in memory to optimize performance by reducing direct system calls.[5] This approach allows for efficient handling of both text and binary data, with characters typically corresponding to bytes in most implementations.[6]
Central to this model is the FILE type, an opaque structure that encapsulates all necessary information about a stream, such as its buffer, position, and state. As an implementation-defined object, the internal members of FILE are not accessible to programs, ensuring that the representation remains hidden and portable.[6] All stream operations are performed using pointers to FILE objects (i.e., FILE *), which serve as handles to the underlying stream without exposing low-level details.[7]
The stream abstraction provided by FILE conceals the operating system's native file descriptors and other platform-specific mechanisms, promoting code portability by standardizing the interface regardless of the host environment.[8] This design aligns with the C standard's emphasis on enabling programs to execute reliably across varied data-processing systems. Predefined streams like stdin, stdout, and stderr are provided as FILE * pointers for console interaction.[3]
Standard Streams
In the C programming language, the standard input/output library defined in the header file<stdio.h> provides three predefined streams as macros that expand to expressions of type FILE *: stdin, stdout, and stderr. These streams serve as the primary interfaces for input and output operations in a C program. stdin is associated with the standard input stream, typically used for reading data from the keyboard or other input sources. stdout corresponds to the standard output stream, intended for conventional output such as displaying results on the screen. stderr is designated for the standard error stream, which handles diagnostic messages and error reporting to ensure they are promptly visible to the user.[9][10][2]
By default, these streams are connected to the program's console: stdin receives unbuffered or line-buffered input from the keyboard, allowing interactive reading until a newline or end-of-file condition. stdout directs output to the screen, where it is line-buffered when connected to a terminal—meaning partial lines are held until a newline is encountered, flushed via fflush, or the program terminates—to support efficient batch processing while enabling real-time display in interactive sessions. In contrast, stderr is typically unbuffered, ensuring immediate reporting of errors without delay, which is crucial for debugging and logging in both interactive and non-interactive environments. The underlying type for these streams is the opaque FILE structure, which abstracts the details of the associated I/O device.[9][10]
In command-line environments, the behavior of these streams supports redirection, a common feature in Unix-like systems where stdout and stderr can be redirected to files (e.g., using > or 2> operators) or piped to other commands (e.g., using |), while stdin can receive input from files or pipes. This allows flexible I/O routing without altering program code, though the exact mechanics are implementation-defined and follow POSIX conventions rather than strict C standard requirements. For instance, redirecting stdout to a file buffers output fully for performance, differing from terminal line-buffering.[2][10]
These streams are automatically initialized and opened at program startup, prior to the invocation of main, and remain open until the program terminates, providing consistent access across portable C implementations compliant with the ISO C standard. Portability is maintained as long as programs do not directly modify the FILE * pointers underlying these macros, which could lead to undefined behavior; instead, functions like freopen are recommended for reassignment if needed. Their persistence ensures reliable console I/O in diverse environments, from embedded systems to full Unix shells.[9][10]
File Management
Opening Files
In C file input/output, opening a file involves associating a stream of typeFILE* with an external file or device, enabling buffered I/O operations. The primary function for this purpose is fopen, which creates a new stream and links it to the specified file.[11]
The syntax of fopen is FILE *fopen(const char *restrict filename, const char *restrict mode);, where filename is a null-terminated string specifying the file's path, and mode is a null-terminated string defining the access mode. On success, it returns a pointer to the FILE object representing the stream; on failure, it returns NULL and sets errno to indicate the error. The FILE structure, an opaque type defined in <stdio.h>, encapsulates the stream's state, including the underlying file descriptor and buffering details. Common mode strings include "r" for read-only access from the beginning, "w" for write-only access (truncating the file if it exists), and "a" for write-only access starting at the end (append mode). Update and binary modes can be specified with "+" (e.g., "r+" for read and write) and "b" (e.g., "rb" for binary read), respectively, while C11 introduces "x" for exclusive creation (failing if the file exists).[11][12]
The following table summarizes the standard modes and their effects:
| Mode | Access Type | File Exists | File Does Not Exist |
|---|---|---|---|
| "r" | Read | Opens for reading from start | Fails |
| "w" | Write | Truncates to zero length and opens for writing | Creates and opens for writing |
| "a" | Append | Opens for writing at end | Creates and opens for writing at end |
| "r+" | Read/Write | Opens for reading and writing from start | Fails |
| "w+" | Read/Write | Truncates to zero length and opens for reading/writing | Creates and opens for reading/writing |
| "a+" | Read/Write | Opens for reading; writing appends to end | Creates and opens for reading; writing appends to end |
#include <stdio.h>
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) {
// Handle error
}
#include <stdio.h>
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) {
// Handle error
}
fopen's functionality: freopen and fdopen. The freopen function, with syntax FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict stream);, first closes the file associated with the existing stream (if any), then opens the new file in the specified mode and reassigns it to that stream, returning the stream pointer on success or NULL on failure. It is commonly used to redirect standard streams, such as reassigning stdin to read from a file instead of the keyboard: freopen("input.txt", "r", stdin);.[13]
The fdopen function, part of the POSIX extension to the C standard library, associates an existing file descriptor (an integer handle from low-level I/O functions like open) with a new stream. Its syntax is FILE *fdopen(int fildes, const char *mode);, returning a FILE* on success or NULL on failure, with modes matching those of fopen. This is useful for integrating unbuffered file descriptors (e.g., from sockets or pipes) with the buffered stdio interface.[14]
Regarding file creation behavior, modes "w", "w+", "a", and "a+" will create the file if it does not exist, with appropriate permissions (typically user read/write, implementation-defined). In "w" or "w+" mode, an existing file is truncated to zero length before writing begins. In "a" or "a+" mode, writes always append to the current end-of-file position, preserving existing content. If the file cannot be created (e.g., due to permissions or disk space), the function fails and returns NULL. The "x" mode (C11) ensures exclusive creation by failing if the file already exists, preventing accidental overwrites.[11][12]
Portability considerations are important when specifying filenames in fopen and related functions, as the C standard does not define path formats or maximum lengths, leaving these implementation-defined. For cross-platform compatibility, use forward slashes (/) as path separators, as they are natively supported on Unix-like systems and accepted by Windows implementations of fopen (which also support backslashes). Maximum path lengths vary: POSIX systems often define PATH_MAX as 4096 bytes, while Windows traditionally limits to 260 characters (MAX_PATH), though modern versions support longer paths with prefixes. Exceeding these limits results in failure, so applications should query system limits (e.g., via pathconf on POSIX) or use dynamic allocation for paths.[11][15]
Closing Files
Thefclose function is used to close a file stream opened by fopen, freopen, or similar functions, ensuring that any unwritten buffered data is flushed to the underlying file and associated resources are released. Its syntax is int fclose(FILE *stream);, where stream is a pointer to the FILE object representing the open stream.[16] Upon successful completion, fclose returns 0; otherwise, it returns EOF to indicate failure, at which point errno may be set to provide details on the error.[17] The function flushes the stream by writing any pending output data, discards any unread input data, disassociates the stream from the file, and frees any automatically allocated buffers, performing the equivalent of a low-level file close operation.[16]
Failing to close files explicitly can lead to resource leaks, as open file descriptors consume system limits until program termination, potentially exhausting available handles in long-running programs that open many files. Additionally, unflushed buffered data may not be written to disk, resulting in potential data loss, especially for output streams where the last buffer contents are not guaranteed to persist without explicit closure. Although the C runtime environment automatically closes all open streams upon normal program exit as if by fclose, explicit closure is recommended to ensure timely flushing and resource management.[16]
When handling multiple files, each stream should be closed individually using fclose in the reverse order of opening to maintain proper resource cleanup, with the return value checked after each call to detect and propagate errors if one closure fails.[17] If an error occurs during one fclose, subsequent operations on other streams can continue, but the failure should be handled to avoid incomplete cleanup across the set of files.[16]
For standard streams like stdin, stdout, and stderr, which are automatically open at program startup, explicit closure with fclose is generally unnecessary, as they are closed by the runtime on program termination.[16] However, it is possible to close them manually if needed, such as in daemon processes to redirect output, though this disassociates the stream and requires reopening if further use is intended.[17]
Data Operations
Reading Data
Reading data from files in C is primarily achieved through functions in the standard I/O library that extract characters or structured data from aFILE stream. These functions support both formatted and unformatted input, allowing developers to parse text or binary data efficiently while advancing the file position indicator. Formatted input interprets the stream according to specified patterns, whereas unformatted input retrieves raw bytes or characters without conversion.[18][19][20]
Formatted Input with fscanf
The fscanf function reads formatted input from a stream, parsing characters according to a format string and storing the results in provided arguments. Its syntax is int fscanf(FILE *restrict stream, const char *restrict format, ...);, where stream is the input file pointer, format specifies the expected structure using ordinary characters and conversion specifiers, and the variable arguments receive the parsed values.[18] Common format specifiers include %d for signed decimal integers (stored in int *), %s for sequences of non-whitespace characters (stored in char *), %f for floating-point numbers (stored in float *), %lf for double-precision floating-point numbers (stored in double *), and %c for single characters or sequences (stored in char *).[18] The function skips leading whitespace for most specifiers except %c and %[, and it stops at the first unmatchable character or end-of-file. It returns the number of successfully assigned items, which may be zero if matching fails early, or EOF if an input failure occurs before any assignment.[18]
For example, to read an integer followed by a string from a file:
#include <stdio.h>
int main(void) {
FILE *stream = fopen("input.txt", "r");
if (stream == NULL) {
return 1; // Handle error
}
int value;
char name[100];
int items = fscanf(stream, "%d %s", &value, name);
if (items == 2) {
printf("Read: %d, %s\n", value, name);
} else {
printf("Failed to read expected items\n");
}
fclose(stream);
return 0;
}
#include <stdio.h>
int main(void) {
FILE *stream = fopen("input.txt", "r");
if (stream == NULL) {
return 1; // Handle error
}
int value;
char name[100];
int items = fscanf(stream, "%d %s", &value, name);
if (items == 2) {
printf("Read: %d, %s\n", value, name);
} else {
printf("Failed to read expected items\n");
}
fclose(stream);
return 0;
}
Unformatted Input
Unformatted input functions retrieve data without applying format conversions, making them suitable for character-by-character access or bulk binary reading. Thefgetc function reads the next byte from the stream as an unsigned character converted to an int, with syntax int fgetc(FILE *stream);. It returns the character value on success or EOF on end-of-file or error, advancing the file position by one.[19] The getc function has the same syntax and behavior but is often implemented as a macro, which may evaluate stream multiple times; thus, it should not be used with expressions having side effects.[19] Both are ideal for simple text processing or when precise control over each byte is needed.
For larger blocks, fread reads up to a specified number of elements, each of a given size, into a buffer. Its syntax is size_t fread(void *restrict ptr, size_t size, size_t nmemb, FILE *restrict stream);, where ptr points to the destination buffer, size is the byte length of each element, and nmemb is the number of elements to read. It internally uses mechanisms similar to fgetc repeated for each byte, treating the data as unsigned characters, and advances the file position accordingly. The function returns the number of fully read elements, which may be less than nmemb if end-of-file or an error intervenes, or zero if size or nmemb is zero.[20]
An example of reading a binary structure:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *stream = fopen("data.bin", "rb");
if (stream == NULL) {
return 1;
}
int numbers[10];
size_t read_count = fread(numbers, sizeof(int), 10, stream);
if (read_count < 10) {
printf("Read only %zu integers\n", read_count);
}
fclose(stream);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *stream = fopen("data.bin", "rb");
if (stream == NULL) {
return 1;
}
int numbers[10];
size_t read_count = fread(numbers, sizeof(int), 10, stream);
if (read_count < 10) {
printf("Read only %zu integers\n", read_count);
}
fclose(stream);
return 0;
}
fread particularly efficient for transferring raw data like arrays or records from binary files.[20]
Reading Lines with fgets
The fgets function is designed for safe line-based reading, retrieving characters into a character array until a newline, end-of-file, or the specified limit is reached. Its syntax is char *fgets(char *restrict s, int n, FILE *restrict stream);, where s is the destination buffer, n is the maximum number of characters to read (including the null terminator), and stream is the input source. It reads at most n-1 characters, appends a null terminator if any characters are successfully read, and includes the newline character in the buffer if encountered before the limit. If the line exceeds n-1 characters, the function reads up to the limit (creating a partial line) and returns the buffer; subsequent calls can continue from that point. It returns a pointer to s on success or NULL on end-of-file (with no characters read) or error, in which case the buffer contents may be indeterminate and not null-terminated.[21]
For instance:
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *stream = fopen("text.txt", "r");
if (stream == NULL) {
return 1;
}
char line[256];
while (fgets(line, sizeof(line), stream) != NULL) {
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0'; // Remove newline if present
}
printf("Line: %s\n", line);
}
fclose(stream);
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *stream = fopen("text.txt", "r");
if (stream == NULL) {
return 1;
}
char line[256];
while (fgets(line, sizeof(line), stream) != NULL) {
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0'; // Remove newline if present
}
printf("Line: %s\n", line);
}
fclose(stream);
return 0;
}
gets, and is preferred for processing text files line by line, such as in parsing configuration or log entries. Handling partial lines requires checking the absence of a newline and reading further if needed.[21]
Performance Considerations
Sequential reading, where data is accessed in contiguous order from the file, generally outperforms random access reading due to optimized buffering in the standard library and reduced overhead from disk seeks or head movements in underlying storage systems.[22] Random access, often involvingfseek before reads, incurs higher latency for non-sequential positions, making it less suitable for large-scale data processing unless the access pattern demands it. These functions typically stop upon end-of-file (EOF) as a natural termination condition.[23]
Writing Data
Writing data to files in C is primarily handled through the standard I/O library functions defined in<stdio.h>, which provide mechanisms for both formatted and unformatted output to file streams opened in write or append modes. These functions allow programmers to insert text or binary data into files, with the stream's buffering typically delaying the actual transfer to the underlying storage until the buffer is flushed or the stream is closed.
Formatted output is achieved using the fprintf function, which writes data to the specified stream according to a format string and variable arguments. Its syntax is int fprintf(FILE *stream, const char *format, ...);, where stream is the target file stream, format is a string containing ordinary characters and conversion specifiers (such as %d for integers, %s for strings, and %f for floating-point numbers), and the ellipsis represents the arguments to be formatted and inserted. The function returns the number of characters successfully written to the stream, or a negative value if an output or encoding error occurs. For example, to write an integer and a string to a file, one might use fprintf(fp, "Value: %d, Name: %s\n", 42, "example");, which outputs the formatted text starting from the current file position.
Unformatted output functions offer lower-level control for writing raw data without interpretation. The fputc function writes a single character to the stream, with syntax int fputc(int c, FILE *stream);, where c is the character (treated as an unsigned char value upon writing), and it returns the character written or EOF on error. A related macro, putc(c, stream), provides equivalent functionality but may evaluate stream multiple times if implemented as a macro, making fputc preferable for efficiency in loops. For bulk binary data, fwrite is used, with syntax size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);, where ptr points to the data block, size is the size of each object in bytes, and nmemb is the number of such objects to write; it returns the number of objects successfully written, which may be less than nmemb on partial failure.[24] This is particularly useful for writing structures or arrays directly, as in fwrite(&data, sizeof(int), 1, fp); for a single integer.[24]
For writing null-terminated strings without formatting, the fputs function is employed, with syntax int fputs(const char *s, FILE *stream);, which outputs the characters of s up to but not including the null terminator. It returns a non-negative integer on success or EOF on error, and unlike some string functions, it does not append a newline automatically—programmers must add \n explicitly if needed. An example usage is fputs("Hello, world!", fp);, which writes the string as-is to the file.
The behavior of write operations—whether they overwrite existing content or append to the end—depends on the mode specified when opening the file with fopen. In write modes such as "w" or "w+", opening truncates the file to zero length if it exists, causing subsequent writes to overwrite from the beginning. In contrast, append modes like "a" or "a+" direct all writes to the end of the file, regardless of the current file position indicator, preserving prior content. This distinction ensures controlled modification of file contents based on the intended operation.
Status and Error Handling
End-of-File Detection
In C file input/output, end-of-file (EOF) detection is essential for gracefully terminating read operations when no more data is available from a stream. The standard library provides thefeof function to query the EOF indicator on a stream, which is set internally by the library when an input operation attempts to read beyond the available data. This indicator distinguishes normal termination due to file exhaustion from errors, allowing programs to handle input loops reliably without assuming the physical structure of the underlying file.[25][26]
The feof function has the syntax int feof(FILE *stream);, where stream is a pointer to the file stream obtained from fopen. It returns a non-zero value if the EOF indicator is set for that stream, and zero otherwise. Importantly, feof is reactive: the EOF indicator is not set until after a read attempt fails at the end of the data, so calling feof before or without a preceding input operation may return zero even if the physical end of the file is imminent. This design ensures that the last valid data is processed before detection occurs.[25][27]
EOF detection interacts closely with standard read functions, where the return value signals potential end-of-file alongside the setting of the indicator. For character-oriented input, fgetc(stream) returns the next character as an int (with values 0-255 for valid characters) or EOF (typically -1) if the end-of-file is reached or an error occurs; upon returning EOF due to end-of-file, it sets the EOF indicator without setting the error indicator. Similarly, fscanf(stream, format, ...) returns the number of successfully assigned input items or EOF at end-of-file, setting the indicator if no further input is available. For binary data, fread(ptr, size, nmemb, stream) returns the number of elements successfully read; if this is less than nmemb due to end-of-file (rather than error), the function sets the EOF indicator, allowing partial reads to signal approaching exhaustion without implying failure. In all cases, the EOF indicator is set only after the read attempt, enabling confirmation via feof post-operation.[19]
The EOF indicator specifically marks when an input operation has reached or passed the physical end of the file's data, where no additional bytes remain to be read. In contrast, stream closure via fclose(stream) does not set the EOF indicator; it simply releases resources and invalidates the stream pointer, preventing further I/O regardless of prior state. This distinction ensures EOF detection applies only during active input on an open stream, independent of closure.[25][26]
Best practices emphasize checking the return value of read functions first to detect incomplete or failed operations, then using feof(stream) to confirm end-of-file versus errors (via ferror(stream)). For example, a safe loop for reading characters avoids infinite loops by testing the read result directly:
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) return 1;
int c;
while ((c = fgetc(fp)) != EOF) {
// Process character c
putchar(c);
}
if (feof(fp)) {
printf("\nEnd of file reached.\n");
}
fclose(fp);
return 0;
}
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) return 1;
int c;
while ((c = fgetc(fp)) != EOF) {
// Process character c
putchar(c);
}
if (feof(fp)) {
printf("\nEnd of file reached.\n");
}
fclose(fp);
return 0;
}
feof checked in advance (e.g., while (!feof(fp)) { fgetc(...); }) would loop once extra after the final successful read, leading to an unnecessary failed attempt. Always clear the EOF indicator with clearerr(stream) if resuming input after detection, though this is rare for sequential file reads.[25][28][26]
Error Detection and Recovery
In C file input/output, errors are detected by examining the error indicator associated with a stream, which is set by I/O functions upon encountering issues such as hardware failures or invalid operations. Theferror() function checks this indicator for a given stream. Its syntax is int ferror(FILE *stream);, where it returns a non-zero value if the error indicator is set and zero otherwise. This function does not distinguish between error types but signals that an I/O operation has failed, separate from the end-of-file condition, which is not considered an error.
To clear the error indicator after detection, allowing further operations on the stream, the clearerr() function is used. Declared as void clearerr(FILE *stream);, it resets both the error and end-of-file indicators for the specified stream without affecting its position. I/O functions like fopen(), fread(), and fwrite() set the error indicator and often return failure values (e.g., NULL for fopen() or EOF for read/write functions), which should always be checked to enable early error detection.
Common errors in file I/O include permission denied (e.g., attempting to write to a read-only file), disk full (insufficient space for writes), and invalid seeks (e.g., seeking beyond file bounds or on non-seekable streams). These are reported through the global errno variable, defined in <errno.h>, which holds an integer code set by the failing function. For instance, EACCES indicates permission denied, ENOENT signals no such file or directory, and ENOSPC denotes no space left on device. To print a human-readable description of the error, the perror() function is employed: void perror(const char *s);, which outputs the string s followed by a colon, space, and the error message corresponding to the current errno value to stderr.[29] Integration with errno ensures portable error reporting across implementations, though exact messages may vary by system.
Recovery from errors typically involves checking return values of all I/O functions immediately after calls, as recommended in the C standard. If an error occurs, strategies include reopening the file with fopen() to obtain a fresh stream, or repositioning the existing stream using rewind(stream) to reset to the beginning or fseek(stream, offset, whence) to seek to a valid position, provided the error is recoverable (e.g., not a permanent disk issue).[30] Persistent errors like hardware faults may require program termination or user intervention, but clearing the indicator with clearerr() post-recovery attempt allows resuming operations where feasible.
Advanced Topics
Text and Binary Modes
In C, file streams can be opened in either text mode or binary mode, specified through the mode string in functions likefopen. The absence of a "b" flag defaults to text mode on systems that distinguish between the two, while appending "b" explicitly requests binary mode; on some implementations, "t" can be used to force text mode, though this is not portable. Combined modes, such as "rt" for read-text or "wb" for write-binary, allow specification of both access type (read, write, append) and mode.[11][12]
Text mode treats the file as a sequence of characters organized into lines, performing automatic translations to conform to the host environment's conventions. Notably, on Windows systems, the newline character \n is translated to the sequence \r\n during writing, and the reverse occurs during reading, which can lead to size differences between the in-memory representation and the on-disk file—for instance, a file with 10 newlines might occupy 20 bytes on disk due to the extra carriage returns. End-of-line handling may also involve ignoring or adding characters, and on some systems like Windows, an end-of-file indicator such as Ctrl+Z (\x1A) is recognized in text mode. These behaviors ensure compatibility with text-oriented displays but introduce implementation-defined variations.[31][11]
In contrast, binary mode preserves the exact sequence of bytes without any translation or modification, making it ideal for non-text data such as images, executables, or serialized structures where fidelity is essential. No newline conversions occur, and the stream is treated as a continuous byte array, allowing direct byte-level access without environmental adjustments. This mode ensures that the number of bytes read matches the number written within the same implementation, though padding with null characters may be added at the end in some cases.[31][11]
Portability between systems is a key concern, as Unix-like (POSIX) environments treat all files as binary by default, with no distinction or translation—the "b" flag has no effect, and newlines are simply \n. Windows, however, enforces the modes, leading to potential mismatches if a file written in text mode on Windows is read in binary mode on Unix or vice versa. To enhance cross-platform compatibility, especially for data files, using binary mode universally is recommended, as it avoids unpredictable translations and maintains consistent byte representation across implementations.[12][11][31]
Buffering Mechanisms
In the C standard I/O library, streams employ three primary buffering strategies to optimize data transfer efficiency while balancing immediacy and resource usage.[32] Fully buffered streams accumulate data in a buffer until it reaches capacity, at which point the contents are written to the underlying file or device in a single operation; this approach is typical for non-interactive files, such as those on disk, where large block transfers minimize overhead.[32] Line-buffered streams, commonly used for interactive output likestdout when connected to a terminal, collect data until a newline character (\n) is encountered or an I/O error occurs, then flush the buffer to ensure timely visibility of partial lines.[32] Unbuffered streams, such as stderr by default, transmit data immediately without intermediate storage, prioritizing low latency for diagnostic or error messages over efficiency.[32]
The setvbuf function provides programmatic control over these buffering behaviors, allowing developers to specify the mode and buffer details after opening a stream but before performing any I/O operations.[32] Its syntax is:
int setvbuf(FILE *restrict stream, char *restrict buf, int mode, size_t size);
int setvbuf(FILE *restrict stream, char *restrict buf, int mode, size_t size);
mode accepts _IOFBF for fully buffered, _IOLBF for line buffered, or _IONBF for unbuffered operation; if buf is a null pointer, the implementation allocates its own buffer (with size serving as a hint), while a non-null buf uses the provided array of size bytes.[32] The function returns 0 on success or a non-zero value on failure, such as an invalid mode or if the stream is already in use.[32] Calling setvbuf after initial I/O invokes undefined behavior, emphasizing the need for early invocation to avoid state inconsistencies.[32]
Buffers are flushed automatically under specific conditions to synchronize stream contents with the host environment: fully buffered streams flush when full, line-buffered streams flush on newline or buffer fullness, and all streams may flush during certain I/O transitions.[32] Manual flushing is achieved via the fflush function, which writes any unwritten output data for the specified stream (or all output streams if the argument is null) and discards unread input data for input streams.[32] Its syntax is:
int fflush(FILE *stream);
int fflush(FILE *stream);
Practical Examples
Basic File Operations
Basic file operations in C involve opening a file for reading, reading its contents line by line, and closing the file stream once done. These operations rely on standard library functions from<stdio.h>. The following example demonstrates opening a text file named "input.txt" in read mode, reading lines using fgets() until the end of the file is reached, printing each line to standard output, and closing the stream.[21]
#include <stdio.h>
int main() {
FILE *fp;
char line[256];
fp = fopen("input.txt", "r");
if (fp == NULL) {
return 1;
}
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
fclose(fp);
return 0;
}
#include <stdio.h>
int main() {
FILE *fp;
char line[256];
fp = fopen("input.txt", "r");
if (fp == NULL) {
return 1;
}
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
fclose(fp);
return 0;
}
#include <stdio.h>, which provides declarations for file handling functions such as fopen, fgets, feof, and fclose. Next, int main() defines the entry point, followed by FILE *fp;, which declares a pointer to a FILE structure for managing the file stream, and char line[256];, an array to store each read line with sufficient size for typical inputs.
The statement fp = fopen("input.txt", "r"); opens the file "input.txt" in read mode ("r"), returning a pointer to the stream on success or NULL on failure. A null check if (fp == NULL) { return 1; } verifies successful opening and exits the program with a non-zero status if it fails, though no further error details are provided here.
The loop while (fgets(line, sizeof(line), fp) != NULL) reads lines using fgets(), which stores up to 255 characters (leaving room for the null terminator) from the stream into line and returns NULL upon reaching the end of the file or an error.[21] Inside the loop, printf("%s", line); outputs the line to standard output. The loop implicitly relies on the stream's end-of-file indicator, set after a read attempt at EOF, to terminate via the fgets() return value.
Finally, fclose(fp); closes the stream, flushing any buffered data and freeing resources associated with it. The return 0; indicates successful program completion.
To test this example, create a file named "input.txt" containing sample text, such as:
Hello, World!
This is a test file.
Hello, World!
This is a test file.
Hello, World!
This is a test file.
Hello, World!
This is a test file.
gcc -o example example.c and execute it using ./example. The program reads and prints the file contents directly without additional input redirection.
Comprehensive Read-Write with Error Checking
A comprehensive read-write operation in C integrates file opening, data reading with formatted or line-based input, processing, output writing, and thorough error detection to ensure robustness. This approach uses functions from the standard I/O library to handle potential failures such as permission issues, disk full conditions, or I/O errors, while distinguishing between end-of-file conditions and actual errors. By checking return values and stream states in loops, the program can recover or exit gracefully, preventing data corruption or incomplete operations. The following example demonstrates a program that reads lines from an input file usingfgets into a dynamically allocated buffer, converts each line to an integer (assuming numeric input), computes its square, and writes the result to an output file using fprintf. It checks for fopen failures with perror, monitors feof and ferror in the read-write loop, and handles allocation errors.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <input_file> <output_file>\n", argv[0]);
return 1;
}
FILE *in = fopen(argv[1], "r");
if (in == NULL) {
perror("Error opening input file");
return 1;
}
FILE *out = fopen(argv[2], "w");
if (out == NULL) {
perror("Error opening output file");
fclose(in);
return 1;
}
char *buffer = malloc(256 * sizeof(char)); // Dynamic buffer for lines up to 255 chars
if (buffer == NULL) {
perror("Memory allocation failed");
fclose(in);
fclose(out);
return 1;
}
while (fgets(buffer, 256, in) != NULL) {
if (ferror(in)) {
perror("Read error");
break;
}
int num = atoi(buffer); // Convert line to int, ignoring non-numeric
if (fprintf(out, "%d\n", num * num) < 0) {
perror("Write error");
break;
}
if (ferror(out)) {
perror("Output stream error");
break;
}
}
if (!feof(in) && ferror(in)) {
perror("Unexpected end-of-file or read error");
}
free(buffer); // Memory management: deallocate buffer
if (fflush(out) != 0) {
perror("Flush error");
}
if (fclose(in) != 0) {
perror("Error closing input file");
}
if (fclose(out) != 0) {
perror("Error closing output file");
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <input_file> <output_file>\n", argv[0]);
return 1;
}
FILE *in = fopen(argv[1], "r");
if (in == NULL) {
perror("Error opening input file");
return 1;
}
FILE *out = fopen(argv[2], "w");
if (out == NULL) {
perror("Error opening output file");
fclose(in);
return 1;
}
char *buffer = malloc(256 * sizeof(char)); // Dynamic buffer for lines up to 255 chars
if (buffer == NULL) {
perror("Memory allocation failed");
fclose(in);
fclose(out);
return 1;
}
while (fgets(buffer, 256, in) != NULL) {
if (ferror(in)) {
perror("Read error");
break;
}
int num = atoi(buffer); // Convert line to int, ignoring non-numeric
if (fprintf(out, "%d\n", num * num) < 0) {
perror("Write error");
break;
}
if (ferror(out)) {
perror("Output stream error");
break;
}
}
if (!feof(in) && ferror(in)) {
perror("Unexpected end-of-file or read error");
}
free(buffer); // Memory management: deallocate buffer
if (fflush(out) != 0) {
perror("Flush error");
}
if (fclose(in) != 0) {
perror("Error closing input file");
}
if (fclose(out) != 0) {
perror("Error closing output file");
}
return 0;
}
FILE* pointers or NULL on failure, with error details printed via perror using the global errno.[29] The loop uses fgets to read lines into a dynamically allocated buffer, checked for allocation success to avoid null pointer dereferences; atoi processes the numeric content simply, though production code might add validation. After each fprintf, the return value (number of characters written) and ferror confirm successful output, distinguishing write failures from end-of-file via feof.
For robust practices, output can be directed to a temporary file using tmpfile to avoid partial writes corrupting the target; upon success, rename atomically replaces the original. Errors can be cleared with clearerr after handling to allow continued operations if recoverable, though this example exits on errors for safety. Before closing streams, fflush ensures all buffered data is written to the underlying file, preventing loss on buffered streams.
Consider a sample input file input.txt containing:
1
2
3
1
2
3
./program input.txt output.txt produces output.txt with:
1
4
9
1
4
9
fopen for input fails, printing "Error opening input file: Permission denied" via perror and exiting without creating output.[29] If the output file's directory allows creation but the target path is a read-only file, fprintf succeeds initially but subsequent fclose or fflush detects the error, printing "Permission denied" and leaving a partial or zero-length output file. In memory-constrained scenarios, malloc failure triggers early exit after closing files and freeing no buffer, ensuring no leaks.
Alternatives to Standard I/O
Low-Level File I/O
Low-level file input/output in C provides unbuffered access to files through system calls that operate on file descriptors, offering direct interaction with the operating system without the abstraction layers of the standard I/O library. These functions, defined in the POSIX standard, enable precise control over file operations, making them suitable for scenarios requiring minimal overhead or custom handling. Unlike buffered streams, low-level I/O requires explicit management of data transfer sizes and error conditions, typically using the globalerrno variable for diagnostics rather than stream-specific indicators like feof() or ferror().
The primary functions for low-level file I/O are open(), read(), write(), and close(), declared in <fcntl.h> for open() and <unistd.h> for the others. The open() function establishes a connection between a file and a file descriptor, taking a pathname, flags (such as O_RDONLY for read-only or O_WRONLY for write-only), and optional mode for creation; it returns a non-negative integer file descriptor on success or -1 on error. The read() function retrieves data from the file associated with the descriptor fd into a buffer buf of size count, returning the number of bytes read (which may be less than count), zero on end-of-file, or -1 on error; its prototype is ssize_t read(int fd, void *buf, size_t count). Similarly, write() attempts to output count bytes from buf to the file, returning the number of bytes written or -1 on error, with prototype ssize_t write(int fd, const void *buf, size_t count). Finally, close() deallocates the file descriptor fd, releasing associated resources and returning 0 on success or -1 on error.
These functions provide advantages such as direct control over I/O operations and avoidance of the buffering and formatting overhead inherent in standard I/O streams, allowing developers to implement tailored buffering strategies for optimal performance. In terms of performance, low-level I/O involves fewer abstraction layers than stdio, potentially reducing latency for large or sequential accesses when manual buffering is applied; however, without such buffering, frequent small operations can incur higher system call overhead compared to stdio's automatic buffering, which batches transfers to minimize physical I/O. Error handling relies on checking return values and consulting errno (declared in <errno.h>), offering granular feedback like EAGAIN for non-blocking operations, in contrast to the more abstracted error reporting in streams.
Low-level file I/O is particularly useful in high-performance applications, such as network servers or data processing pipelines, where fine-grained control over descriptors enables efficient multiplexing, and in embedded systems where resource constraints make stdio's overhead undesirable, as these environments often prioritize minimal memory and CPU usage over convenience. Portability is limited to POSIX-compliant systems, such as Unix-like operating systems, and these functions are not part of pure ANSI/ISO C, which lacks direct support for file descriptors; code using them may require conditional compilation or wrappers for non-POSIX platforms like Windows. Streams can be created from descriptors using fdopen() for hybrid approaches, but this introduces some stdio overhead.
