11 File I/O
1. Introduction to File I/O
1.1 What is File I/O?
File Input/Output (I/O) is the process of reading from and writing to files. In programming, especially in C, it's a fundamental concept that allows your program to persist data, or read data from external sources for various applications.
1.2 Importance in Embedded Systems
In embedded systems, File I/O is a bit of a nuanced subject. While some embedded systems have no filesystem and thus no file I/O, others do. Those that do could use file I/O for a variety of reasons, such as:
- Configuration: Storing system settings that persist between restarts.
- Data Logging: Recording data for later analysis, which is crucial in many industrial applications.
- Firmware Updates: Storing new firmware images before a system update.
- Resource Storage: Holding assets like language files, UI elements, or cryptographic keys.
2. Standard I/O vs. Low-Level I/O
When it comes to file input/output (I/O) in C, you generally have two broad categories of functions that you can use: Standard I/O and low-level I/O. Each has its own set of advantages and disadvantages, particularly in the context of embedded systems. The following sections will discuss each in more detail.
2.1 Standard I/O
Standard I/O is part of the C Standard Library and includes functions like fopen
, fread
, fwrite
, and fclose
. This level of I/O is buffered, meaning that it reads or writes data in chunks rather than one byte at a time, which can improve performance.
Example:
FILE *fp = fopen("file.txt", "r");
char buffer[100];
fread(buffer, 1, 100, fp);
fclose(fp);
Advantages
- Easier to use and more portable.
- Buffered I/O can offer performance benefits.
- Rich set of features like formatted I/O (
fprintf
,fscanf
).
Disadvantages
- Buffering can be a disadvantage in real-time systems where timing is critical.
- Consumes more memory due to buffering, which might be a concern in embedded systems.
2.2 Low-Level I/O
Low-level I/O involves system calls such as open
, read
, write
, and close
. These functions are generally part of the OS API and offer more direct control over the hardware.
Example:
#include <fcntl.h>
int fd = open("file.txt", O_RDONLY);
char buffer[100];
read(fd, buffer, 100);
close(fd);
Advantages
- Offers more control, which is often necessary in embedded systems.
- Generally faster for single-byte or unbuffered operations.
- More suitable for real-time systems where exact timing of data transfer is critical.
Disadvantages
- Less portable, as the available system calls may vary between operating systems.
- More complex to use correctly, often requiring more error handling.
3. Standard I/O Operations
3.1 Opening Files
Before any I/O operations, you must open the file. The fopen()
function is used for this purpose. It takes the filename and the mode as arguments.
FILE *fp = fopen("example.txt", "r"); // Open for reading
Here, fp
is a pointer to a FILE
type that will be used for subsequent I/O operations. The mode can be "r"
for reading, "w"
for writing, "a"
for appending, among others.
3.2 Reading from Files
To read from a file, you can use functions like fread()
or fgets()
.
char buffer[100];
fread(buffer, 1, sizeof(buffer), fp); // syntax: fread(buffer, size_bytes, element_count, file_pointer)
Or, for reading lines:
fgets(buffer, sizeof(buffer), fp); // syntax: fgets(buffer, buffer_size, file_pointer)
3.3 Writing to Files
Writing is similar to reading and can be done with functions like fwrite()
or fprintf()
.
char data[] = "Hello, World!";
fwrite(data, 1, sizeof(data), fp); // syntax: fwrite(data, size_bytes, element_count, file_pointer)
Or, for formatted writing:
fprintf(fp, "The answer is %d\n", 42); // syntax: fprintf(file_pointer, format_string, data)
3.4 Closing Files
After you're done with a file, it's good practice to close it using fclose()
.
fclose(fp); // Close the file
Closing files is especially important in embedded systems where resources are limited. Failing to close a file can lead to memory leaks and other unexpected behavior.
3.5 Formatted I/O
Formatted I/O is a powerful feature of Standard I/O that allows you to read and write data in a formatted manner. This is particularly useful for parsing configuration files or writing log files.
Reading Formatted Data
The fscanf
function allows you to read formatted data from a file. For example, you can read a string and an integer from a file like this:
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
// Handle error
return 1;
}
char name[50];
int age;
fscanf(fp, "Hello, %s. You are %d years old.", name, &age);
printf("Name: %s, Age: %d\n", name, age);
fclose(fp);
Suppose file.txt
contains "Hello, Alice. You are 30 years old." This code would read that data into the name and age variables, and then print "Name: Alice, Age: 30".
Writing Formatted Data
The fprintf
function allows you to write formatted data to a file. For example, you can write a string and an integer to a file like this:
FILE *fp = fopen("file.txt", "w");
if (fp == NULL) {
// Handle error
return 1;
}
fprintf(fp, "Hello, %s. You are %d years old.\n", "Alice", 30);
fclose(fp);
In this example, the text "Hello, Alice. You are 30 years old." would be written to file.txt
.
4. Low-Level I/O Operations
Low-level I/O operations are closer to the system hardware and often bypass standard library buffering. These are especially relevant in embedded systems where direct control over I/O is essential. The system calls for these operations are usually open
, read
, write
, and close
.
4.0 Including the Header
To use low-level I/O, you'll need to include the <fcntl.h>
header, which stands for "file control". This header contains the necessary definitions for the system calls.
#include <fcntl.h>
4.1 Opening Files
Use the open
system call to open a file. This returns a file descriptor, an integer used to identify the opened file.
int fd = open("example.txt", O_RDONLY); // Open for reading
The flags for opening modes include O_RDONLY
for read-only, O_WRONLY
for write-only, and O_RDWR
for read-write, among others.
4.2 Reading from Files
For low-level reading, use the read
system call:
ssize_t bytesRead;
char buffer[100];
bytesRead = read(fd, buffer, sizeof(buffer));
Here, bytesRead
will contain the number of bytes actually read, which may be less than sizeof(buffer)
.
4.3 Writing to Files
Low-level writing can be done using the write
system call.
ssize_t bytesWritten;
char data[] = "Hello, World!";
bytesWritten = write(fd, data, sizeof(data));
bytesWritten
will contain the number of bytes that were actually written, which could be less than the requested number.
4.4 Closing Files
Closing files in low-level I/O is done using the close
system call.
close(fd); // Close the file
Similar to high-level I/O, it's crucial to close files when they are no longer needed, especially in resource-constrained environments like embedded systems.
5. File Modes
File modes dictate how a file will be accessed, such as read-only, write-only, or both. It's also crucial to distinguish between text and binary modes.
5.1 Text vs. Binary
In text mode, the system performs certain translations (like '\n'
to '\r\n'
on Windows). In binary mode, no such translations are performed; data is read/written as-is.
FILE *fp1 = fopen("text.txt", "rt"); // Text mode
FILE *fp2 = fopen("data.bin", "rb"); // Binary mode
5.2 fopen Modes
Mode | Access Type | Precondition | File Pointer Position | Preserves Content? |
---|---|---|---|---|
r |
Read-only | File must exist | Beginning | Yes |
w |
Write-only | Creates new or truncates existing file | Beginning | No |
a |
Append | Creates file if it doesn't exist | End | Yes |
r+ |
Read and write | File must exist | Beginning | Yes |
w+ |
Read and write | Creates new or truncates existing file | Beginning | No |
a+ |
Read and write | Creates file if it doesn't exist | End for writing, anywhere for reading | Yes |
Note: Suffix a b
for binary modes, like rb
or wb
.
6. Buffering
Buffering techniques can optimize I/O operations but also bring complexity that needs careful management.
6.1 Buffered vs. Unbuffered I/O
-
Buffered I/O: Temporarily stores data in a buffer before reading or writing to the system. This minimizes system calls, which are often expensive, thereby improving performance.
-
Unbuffered I/O: Performs I/O operations directly, without any temporary storage buffer. This gives you greater control but may be slower due to frequent system calls.
6.2 Customizing Buffer Size
In C's Standard Library, you can use setbuf
or setvbuf
to set the buffer size for a file stream.
-
setbuf(FILE *fp, char *buf)
: Automatically sets the buffer size toBUFSIZ
, which is a system-defined constant. -
setvbuf(FILE *fp, char *buf, int mode, size_t size)
: More flexible; allows you to specify the buffer mode (_IOFBF
,_IOLBF
,_IONBF
) and size.
6.3 Setting Buffer to Zero
setvbuf(fp, NULL, _IONBF, 0)
: This disables buffering. All reads and writes will be unbuffered, meaning each read or write operation will directly call the underlying system functions (the low-level I/O functions we discussed earlier). This is useful for real-time systems where timing is critical.
6.4 Trade-offs
-
Speed vs. Control: Buffered I/O is generally faster but offers less control. Unbuffered I/O is slower but allows for greater precision in I/O operations.
-
Memory Usage: Using larger buffers can speed up I/O but consume more memory.
-
Data Integrity: Forgetting to flush the buffer can result in data loss or corruption, especially in cases like system crashes or program termination. Use
fflush(fp)
to manually flush the buffer.
6.5 Flushing the Buffer
Flushing sends the data from the buffer to its intended destination. It's particularly important when:
- Switching from writing to reading in read/write ("r+") mode. This is because the buffer is still in write mode and needs to be flushed before reading.
- You need to ensure that written data is physically stored.
You can flush the buffer using fflush(fp);
.
In read operations, the system handles buffering more transparently, usually filling the buffer as you read. Flushing is generally not needed unless you're working with a read/write ("r+") stream and you're switching from writing back to reading. In that case, flushing ensures that the data you've written is physically stored before you continue reading from the same file stream.
7. File Positioning
File positioning functions like fseek
, ftell
, and rewind
help you navigate within a file. These are particularly useful when you're working with large files or need random access.
7.1 fseek
The fseek
function sets the file position indicator for the stream. It takes three parameters:
- A file pointer
- The offset (in bytes)
- The position from where the offset is added (SEEK_SET, SEEK_CUR, or SEEK_END)
fseek(fp, 10, SEEK_SET); // Move 10 bytes ahead from the beginning
7.2 ftell
ftell
gives you the current position of the file pointer. It's often used to check the size of a file or to save a position for later.
long position = ftell(fp); // Get current position
7.3 rewind
The rewind
function moves the file pointer to the beginning of the file, essentially doing the same as fseek(fp, 0, SEEK_SET)
but in a clearer manner.
rewind(fp); // Go back to the beginning
8. File and Directory Management
Managing files and directories goes beyond just reading and writing. You might need to create, delete, or rename files and directories depending on your application's requirements. Here's how you can do it:
8.1 Creating, Deleting, and Renaming Files
Creating Files
You can create a new file by using fopen
with the mode set to w
. If the file doesn't exist, it'll be created.
FILE *fp = fopen("newfile.txt", "w");
Deleting Files
You can delete a file using the remove
function.
int status = remove("oldfile.txt");
Renaming Files
The rename
function allows you to change a file’s name.
int status = rename("oldname.txt", "newname.txt");
8.2 Working with Directories
On systems that support it, you can use functions like mkdir
, rmdir
, and chdir
to manage directories.
Creating Directories
int status = mkdir("newdir");
Removing Directories
int status = rmdir("olddir");
Changing Directories
int status = chdir("newdir");
Note that these functions may not be available on all embedded systems, as they often require a more complete operating system. Always check your platform's documentation to see what's supported.
8. Use Cases in Embedded Systems
File I/O is not just for desktop programs; it has several important roles in embedded systems too. Below are some of the most common use cases.
8.1 Configuration File Parsing
Embedded systems often have configuration files that define system behavior. For example, a sensor's sampling rate might be configurable via a text file on a memory card. Reading this file during system initialization allows for easier updates and customization.
// Sample code to read a config file and set system parameters
FILE *fp = fopen("config.txt", "r");
if (fp) {
fscanf(fp, "sampling_rate=%d", &sampling_rate);
fclose(fp);
}
8.2 Logging
Logging is crucial for debugging and monitoring system performance. In embedded systems, logs might be written to a local file system, sent over a network, or stored on an SD card.
// Sample code to log data
FILE *logFile = fopen("log.txt", "a");
if (logFile) {
fprintf(logFile, "Temperature: %d\n", temp);
fclose(logFile);
}
8.3 Data Storage
Local data storage is often necessary for embedded systems. For example, a digital camera will store pictures on an SD card, or a data logger may store sensor readings for later analysis.
// Sample code to store sensor data
FILE *dataFile = fopen("data.bin", "wb");
if (dataFile) {
fwrite(sensor_data, sizeof(sensor_data), 1, dataFile);
fclose(dataFile);
}
9. Error Handling
9.1 errno
The global variable errno
is set by system calls and some library functions in the event of an error to indicate what went wrong.
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
printf("Error: %d\n", errno);
}
9.2 perror and strerror
perror()
and strerror()
are useful functions for printing the error message corresponding to errno
.
if (fp == NULL) {
perror("Failed to open file");
// OR
printf("Failed to open file: %s\n", strerror(errno));
}
10. Optimization Techniques
Optimizing File I/O can lead to noticeable performance gains in embedded systems.
10.1 Minimizing Disk Access
Minimizing disk access can speed up I/O operations. For example, reading or writing data in chunks is generally faster than one byte at a time.
char buffer[1024];
fread(buffer, sizeof(char), 1024, fp);
However, this may not be possible in all cases, such as when you're working with a serial port or a network socket. Also, fread
is already buffered, so you may not see much of a performance gain here.
10.2 Efficient Buffering
Using buffering effectively can minimize the I/O function call overhead. You can set the buffer size according to your needs using setvbuf()
.
setvbuf(fp, buffer, _IOFBF, sizeof(buffer));
11. Best Practices
11.1 File Closing and Resource Management
Always close files when you are done with them to free up system resources.
if (fp) {
fclose(fp);
}
11.2 Error Checks
Always check the return values of functions and take appropriate action when something goes wrong.
if (fwrite(data, sizeof(data), 1, fp) != 1)
{
perror("Failed to write data");
}
Here, fwrite
returns the number of elements successfully written, which should be 1 in this case. If it's not, then something went wrong and you should handle the error.
12. Q&A
1. Question:
What are the primary differences between standard I/O functions (like fopen
, fprintf
) and low-level I/O functions?
Answer:
Standard I/O functions are part of the C library and provide buffered access to files and devices, making them efficient for multiple small I/O operations. They also handle data conversion (e.g., converting integers to text and vice versa).
Low-level I/O functions, on the other hand, directly interact with the operating system's I/O services without buffering or data conversion. They work with file descriptors instead of file pointers and include functions like open
, read
, and write
.
2. Question:
How can one set a file to be in binary mode using the standard I/O functions in C?
Answer:
When opening a file using fopen
, you can append a b
to the mode string. For example, to open a file for binary reading, you'd use fopen(filename, "rb")
.
3. Question:
In the context of file operations, what does "buffering" mean?
Answer:
Buffering refers to the practice of temporarily storing data in memory (the buffer) before writing it to a file or after reading it from a file. This helps improve I/O performance, as accessing memory is generally faster than performing direct I/O operations.
4. Question:
What's the purpose of the fflush
function in file operations, and when should you use it?
Answer:
fflush
is used to force the buffer to be written to the actual file or device. This is useful when you want to ensure that all buffered data has been committed, for instance, before reading from a file that you've just written to or before closing a file.
5. Question:
What function would you use to determine the current position of the file pointer in a file?
Answer:
You can use the ftell
function, which returns the current position of the file pointer relative to the beginning of the file.
6. Question:
How would you open a file in "append" mode using standard I/O functions, and why might this mode be useful?
Answer:
You'd use fopen(filename, "a")
to open a file in append mode. This mode is useful because any data written to the file is added to its end, without overwriting any existing data. It's handy for logging and situations where you continuously add data to a file.
7. Question:
Explain the difference between fgetc
and fread
when reading from a file.
Answer:
fgetc
reads a single character from the given file stream, whereas fread
can read multiple bytes (or elements) in a single call. While fgetc
is generally used for text files, fread
is more versatile and can be used for both text and binary files.
8. Question:
If you're working on an embedded system with limited memory, what are some best practices to follow when working with File I/O?
Answer:
- Use buffering judiciously: While buffering can speed up I/O operations, it can also consume precious memory. Adjust buffer sizes as per the system's memory constraints.
- Close files promptly after use to free up system resources.
- Avoid keeping multiple files open simultaneously if not necessary.
- When reading or writing large files, process them in chunks to avoid memory exhaustion.
- Monitor file operations for errors, as limited resources can lead to I/O issues.
9. Question:
In the context of embedded systems, why might you prefer low-level I/O functions over standard I/O?
Answer:
Low-level I/O functions offer more direct control over file operations, which can be crucial in resource-constrained environments like embedded systems. They also skip the overhead of buffering and data conversion, which can save memory and processing time.
10. Question:
When might file positioning functions like fseek
and ftell
be particularly useful in embedded systems?
Answer:
File positioning functions are valuable in scenarios where random file access is needed, like when updating specific records in a database file or accessing configuration settings located at known offsets. This can be especially handy in embedded systems where full-file operations might be resource-intensive.
11. Question:
When might using text mode ("t"
) during file operations introduce potential issues, especially when transferring files between different systems?
Answer:
Text mode can introduce issues during file operations when transferring files between systems with different newline conventions, such as Windows (\r\n
) and UNIX/Linux (\n
). Reading a file in text mode on a system might interpret or convert the newline characters differently than they were written, which could corrupt the file or change its content.
12. Question:
How would you implement a function to check if a file exists in the file system without actually opening it?
Answer:
You could use the stat
or access
function. While stat
provides detailed file information, access
simply checks for the file's presence and permissions. Both avoid opening the file.
13. Question:
In the context of embedded systems, why might memory-mapped file I/O be advantageous compared to standard file I/O functions?
Answer:
Memory-mapped file I/O allows a file to be treated as if it were in memory. This facilitates faster data access since it bypasses the standard system calls and buffering. It's particularly beneficial in embedded systems for quick data manipulation and when working with large files.
14. Question:
Describe a scenario where the use of the fsetpos
and fgetpos
functions might be preferred over fseek
and ftell
.
Answer:
fsetpos
and fgetpos
deal with the fpos_t
data type, which may store more than just the file position (e.g., state information for multi-byte character parsing). They're preferred in environments where the file position exceeds what a long int
(used by fseek
and ftell
) can represent or when specific state info must be preserved.
15. Question:
How can one detect an end-of-file condition when using the fread
function?
Answer:
While fread
returns the number of items successfully read, it doesn't directly indicate an end-of-file. To detect EOF, you can check the feof
function after calling fread
. If feof
returns a non-zero value, it means the end of the file has been reached.
16. Question:
What could go wrong if you don't close a file after operations?
Answer:
Not closing a file can lead to:
- Memory leaks.
- File corruption due to incomplete buffer writes.
- Exhaustion of file descriptors, limiting further file operations.
- Undefined behavior if the file is accessed by other processes or tasks.
17. Question:
Can the fprintf
function be used to write binary data? Why or why not?
Answer:
While fprintf
is primarily for formatted text output, it's possible to write binary data using specific format specifiers. However, it's not the best choice due to potential character conversions, especially newline characters. For binary data, functions like fwrite
are more appropriate.
18. Question:
Imagine an embedded system with non-volatile storage that undergoes frequent power losses. What file I/O considerations should you have?
Answer:
In such scenarios, considerations might include:
- Ensuring atomic writes or using a journaling system to avoid file corruption.
- Regularly flushing buffers with fflush
.
- Implementing wear-leveling techniques if the storage is flash memory to prolong lifespan.
- Avoiding frequent write operations, which can wear out the storage.
19. Question:
How can you ensure that two tasks or threads don't simultaneously access a file, leading to data corruption?
Answer:
You can use synchronization mechanisms like mutexes (mutual exclusion) or semaphores to ensure that only one task or thread accesses the file at a time, preventing concurrent writes or reads.
20. Question:
If you need to read a line from a file but don't know its length in advance, how can you handle this situation, especially in a memory-constrained embedded system?
Answer:
You can use functions like fgets
that read up to a specified number of characters or until a newline or EOF. In a memory-constrained system, you'd read the line in chunks, process each chunk, and then continue reading, rather than trying to load the entire line into memory.
21. Question:
How would you prevent a file from being accessed simultaneously by multiple functions?
Answer:
Use a lock mechanism, like a mutex, to ensure exclusive access to the file.
22. Question:
Why might you prefer fread
and fwrite
over fgetc
and fputc
in an embedded environment?
Answer:
fread
and fwrite
allow for bulk transfer of data, making them more efficient than byte-wise operations like fgetc
and fputc
.
23. Question:
In what scenario would you use the fsync
function after writing to a file?
Answer:
Use fsync
when you need to ensure that written data is flushed to the storage device immediately, providing data durability.
24. Question:
How would you handle errors when a call to fopen
fails?
Answer:
Check the global errno
variable, and use functions like perror
or strerror
to obtain a description of the error.
25. Question:
What is the significance of the volatile
keyword when dealing with file operations in embedded systems?
Answer:
While volatile
is not directly related to file operations, in embedded systems, it ensures that the compiler doesn't optimize away successive reads or writes to a variable. This is crucial when dealing with memory-mapped I/O or hardware registers, but less so with standard file operations.
26. Question:
How does buffering impact file I/O operations, and how can you control it?
Answer:
Buffering can improve I/O performance by reducing the number of system calls. The setbuf
and setvbuf
functions allow you to control buffering behavior.
27. Question:
Describe a scenario where you would need to use the ftell
and fseek
functions.
Answer:
When randomly accessing positions within a file, such as updating specific records in a binary file, ftell
gives the current position, and fseek
moves to a desired position.
28. Question:
What are the differences between fopen
modes "a+" and "r+"?
Answer:
"a+" opens the file for reading and appending; it creates the file if it doesn't exist. "r+" opens the file for reading and writing, but it doesn't create a new file if one doesn't exist.
29. Question:
Why is direct memory access (DMA) sometimes used in conjunction with file I/O in embedded systems?
Answer:
DMA allows data transfer between memory and peripherals without CPU intervention, which can greatly speed up file I/O operations in embedded systems.
30. Question:
How would you differentiate between end-of-file and an error using the fgetc
function?
Answer:
Both situations return EOF
. To differentiate, after receiving EOF
, check feof
(end-of-file condition) and ferror
(error condition) functions.
41. Question:
What's the mistake in the following code?
FILE *fp = fopen("file.txt", "r");
char content[50];
if (fp != NULL) {
fread(content, sizeof(char), 50, fp);
}
fclose(fp);
Answer:
The fclose(fp)
should be inside the if
statement, after the fread
. If fopen
fails, attempting to close an invalid file pointer can lead to undefined behavior.
42. Question:
Identify the error in this code snippet:
FILE *fp = fopen("data.txt", "w");
if (fp) {
fputs("Sample data", fp);
}
fclose(fp);
fp = NULL;
fputs("More data", fp);
Answer:
There's an attempt to write to a NULL
file pointer using fputs
at the end. This will result in undefined behavior.
43. Question:
Spot the flaw here:
FILE *fp = fopen("notes.txt", "r");
if (fp) {
char c = fgetc(fp);
while (c != EOF) {
// process c
c = fgetc(fp);
}
}
fclose(fp);
Answer:
The variable c
should be of type int
to correctly handle the EOF
condition. Using char
might result in an infinite loop if EOF
corresponds to a valid char value.
44. Question:
What's wrong with the following code?
FILE *fp = fopen("output.txt", "w");
for (int i = 0; i < 100; i++) {
fprintf(fp, "%d\n", i);
fclose(fp);
fp = fopen("output.txt", "w");
}
Answer:
The file is being opened and closed inside the loop in write mode. This will overwrite the file on each iteration, resulting in only the last number (99) being written.
45. Question:
Identify the issue in this code:
char *filename = "data.txt";
FILE *fp = fopen(filename, "w");
filename[0] = 'b';
fputs("Sample content", fp);
fclose(fp);
Answer:
The filename string is modified after opening the file. While it doesn't cause an error with the current operations, it's a risky practice that can lead to confusion and potential bugs in more complex scenarios.
46. Question:
Spot the problem in this snippet:
FILE *fp;
fp = fopen("log.txt", "a");
fprintf(fp, "Log entry");
Answer:
The file is never closed using fclose(fp)
. This can lead to resource leaks and potential data loss if the buffer isn't flushed.
47. Question:
What's the flaw in the following?
FILE *fp = fopen("records.txt", "w");
char data[100];
fgets(data, 100, stdin);
fwrite(data, sizeof(char), strlen(data), fp);
fclose(fp);
Answer:
There's no check to see if fopen
was successful. Before writing to fp
, there should be a check like if(fp != NULL)
to ensure it's valid.
48. Question:
Given this code, identify the mistake:
FILE *fp = fopen("sample.txt", "r");
char buffer[10];
fread(buffer, sizeof(char), 20, fp);
fclose(fp);
Answer:
The fread
function tries to read 20 characters into a buffer that can only hold 10. This results in a buffer overflow.
49. Question:
Examine the following:
FILE *fp1 = fopen("A.txt", "r");
FILE *fp2 = fopen("A.txt", "w");
char c;
while((c = fgetc(fp1)) != EOF) {
fputc(c, fp2);
}
fclose(fp1);
fclose(fp2);
Answer:
The file "A.txt" is being opened simultaneously for reading and writing. This can cause undefined behavior, especially since reading and writing operations are interleaved.
50. Question:
What's the problem with the following code?
FILE *fp = fopen("file.txt", "r+");
if (fp) {
fputs("Hello, World!", fp);
rewind(fp);
char data[50];
fgets(data, 50, fp);
}
fclose(fp);
Answer:
The code writes "Hello, World!" and then immediately tries to read it back. However, the internal buffer might not have been flushed before the read operation, so fgets
might not retrieve the recently written data. Using fflush(fp)
after writing would ensure the data is written to the file before reading it back.