I was solving volume (one of the questions in cs50x). Apparently the code that I wrote, works when the file contents are read in an array and from that array, written into the output file.
When I tried to read the input file contents to the output file, by giving a pointer to the output file, it didn't pop any error, but the output was not working.
Can someone explain how reading from a file to a location (say an array) and reading from one file into another file are different.
THIS IS THE CODE FOR REFERENCE
`// Modifies the volume of an audio file
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// Number of bytes in .wav header
const int HEADER_SIZE = 44;
int main(int argc, char *argv[])
{
// Check command-line arguments
if (argc != 4)
{
printf("Usage: ./volume input.wav output.wav factor\n");
return 1;
}
// Open files and determine scaling factor
FILE *input = fopen(argv[1], "r");
if (input == NULL)
{
printf("Could not open file.\n");
return 1;
}
FILE *output = fopen(argv[2], "w");
if (output == NULL)
{
printf("Could not open file.\n");
return 1;
}
float factor = atof(argv[3]);
// TODO: Copy header from input file to output file
uint8_t arr[HEADER_SIZE];
fread(arr, sizeof(uint8_t), HEADER_SIZE, input);
fwrite(arr, sizeof(uint8_t), HEADER_SIZE, output);
// creating a temprary location in memory for the content bytes of a wave file
int16_t buffer;
// TODO: Read samples from input file and write updated data to output file
while (fread(&buffer, sizeof(int16_t), 1, input))
{
buffer = (buffer) * factor;
fwrite(&buffer, sizeof(int16_t), 1, output);
}
// Close files
fclose(input);
fclose(output);
}`
But if I wrote the code below, it won't work
` // TODO: Copy header from input file to output file
// reading from the input file to the output file.
fread(output, sizeof(uint8_t), HEADER_SIZE, input);
// creating a temprary location in memory for the content bytes of a wave file
int16_t buffer;
// TODO: Read samples from input file and write updated data to output file
while (fread(&buffer, sizeof(int16_t), 1, input))
{
buffer = (buffer) * factor;
fwrite(&buffer, sizeof(int16_t), 1, output);
}`
Can someone explain how reading from a file to a location (say an array) and reading from one file into another file are different.
CodePudding user response:
In your second sample code you do a file read from input
to output
. In this specific case, output
is considered as a void *
buffer to store data ; not as a file handle to write data to.
fread(output, sizeof(uint8_t), HEADER_SIZE, input);
You should rather consider something like this:
uint8_t buffer[HEADER_SIZE];
size_t read, written;
read = fread(buffer, 1, HEADER_SIZE, input);
written = fwrite(buffer, 1, read, output);
NOTE: for simplicity I do not check return values here, I leave it up to the OP.
EDIT:
1 - What is FILE pointer?
A FILE *
is a pointer to a dynamically allocated FILE
structure. This structure holds specific pieces of information used by the API to determine which file is concerned by read, write, seek, etc.
2 - Why doesn't writing to file handle works?
The fread
function takes a pointer as first parameter. A FILE *
is a pointer indeed. However, fread
expects a memory region to write data to.
Passing a FILE *
will be accepted by the compiler since every pointers cast okay to void *
.
However, during runtime, the passed FILE *
pointer will be used for storing read data bytes as a memory buffer. This means that fread
will override the target memory structure with read content starting from the pointed address.
In the end, the FILE *
structure will no longer hold the API data and thus, when the user will use the FILE *
pointer for writing data to a file, the original FILE *
being corrupted, fwrite
will most likely lead to a segfault.
CodePudding user response:
See this answer to understand what FILE*
entails and why passing a pointer to a different type doesn't work as expected. More detail on the FILE
structure here.
CodePudding user response:
From the man page:
The function fread() reads nmemb items of data, each size bytes long, from the stream pointed to by stream, storing them at the location given by ptr.
Signature:
size_t fread(void *restrict ptr, size_t size, size_t nmemb,
FILE *restrict stream);
The first argument of fread
is a void pointer that is to be written to. You passed it a FILE *
in your code.
// reading from the input file to the output file.
fread(output, sizeof(uint8_t), HEADER_SIZE, input);
On a Unix-based system, you could map the file into memory with mmap
and then work with it. Or just read the file into a buffer similar to how you did in your first example.
FILE
is a structure defined in the standard library. It contains all the relevant information about the opened file.
From C11:
(...) FILE which is an object type capable of recording all the information needed to control a stream, including its file position indicator, a pointer to its associated buffer (if any), an error indicator that records whether a read/write error has occurred, and an end-of-file indicator that records whether the end of the file has been reached; (...)
fopen
returns a pointer to a FILE
structure. The pointer itself is not pointing to the memory in the file, it's pointing to a structure of type FILE
.
The contents of that structure are completely implementation defined.
Here's what FILE
entails on my system:
type = struct _IO_FILE {
int _flags;
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base;
char *_IO_write_ptr;
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
char _unused2[20];
} *