1

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.

  • 2
    There is no way in C to directly "read from one file into another file". Reads and writes are always from/to disk files, and to/from memory. `fread` reads bytes from a disk file and copies them into memory. `fwrite` takes bytes from memory and writes them to a disk file. Depending on your needs, you can use different in-memory data structures: single variables, structures, arrays, etc. – Steve Summit Jan 04 '23 at 15:55
  • it does not show any error, but the output audio file does not work (play any audio) – Sahil Gautam Jan 04 '23 at 15:57
  • @SteveSummit Ah, but [sendfile()](https://man7.org/linux/man-pages/man2/sendfile.2.html) comes close to doing that - file data is copied directly and never reaches user space. ;-) – Andrew Henle Jan 04 '23 at 16:50
  • 1
    @AndrewHenle I was about to say, "OMFG, I had forgotten/repressed that!", but no, I don't have to, I left myself an out, I said there was no way *in C*. :-) – Steve Summit Jan 04 '23 at 16:53
  • @SteveSummit True. `sendfile()` isn't even POSIX. AFAIK, it's just a Linux/Solaris extension. – Andrew Henle Jan 04 '23 at 16:54

3 Answers3

3

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.

Jib
  • 1,334
  • 1
  • 2
  • 12
  • The answer doesn't really explain anything concerning why the direct read/write operation using ```fread``` shouln't work. "In this specific case, output is considered as a void * buffer to store data ; not as a file handle to write data to.": so, what? Isn't the data written anyway to the output? Why no compiler or runtime error occurs? I think the answer would be much better if it explained what a FILE pointer actually is/does. – picchiolu Jan 04 '23 at 16:04
  • 1
    @picchiolu Thanks for pointing that out. I updated the answer accordingly. – Jib Jan 04 '23 at 16:29
  • 1
    `written = fwrite(buffer, 1, HEADER_SIZE, output)` probably should be `written = fwrite(buffer, 1, read, output)`, absent error checking. – Andrew Henle Jan 04 '23 at 16:52
  • @AndrewHenle Indeed ! – Jib Jan 04 '23 at 16:53
0

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];
} *
Harith
  • 4,663
  • 1
  • 5
  • 20
  • Why does the difference between FILE* and void* prevents the data from being written? Why no error raised? – picchiolu Jan 04 '23 at 16:05
  • these answers seem to be from robots. – Sahil Gautam Jan 04 '23 at 16:20
  • There is no way in C to directly "read from one file into another file". Reads and writes are always from/to disk files, and to/from memory. `fread` reads bytes from a disk file and copies them into memory.`fwrite` takes bytes from memory and writes them to a disk file. Depending on your needs, you can use different in-memory data structures: single variables, structures, arrays, etc. – Steve Summit I don't know how to pin the comments as this explains what I asked for – Sahil Gautam Jan 04 '23 at 16:21
  • @SahilGautam I do not believe my answer was contrary to what he said. And yes, I'm a robot. – Harith Jan 04 '23 at 16:42
  • @picchiolu I edited my answer to include relevant information about a ```FILE``` pointer. I didn't find any information about what happens when you write data to it, and I do not feel like guessing. But perhaps, you could enlighten me? – Harith Jan 04 '23 at 16:43
0

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.

picchiolu
  • 1,120
  • 5
  • 20