Search code examples
cmemorydynamic-memory-allocationcs50fwrite

Why does cs50's "Volume" lab use a single variable "buffer" in fread() instead of using malloc() or an array?


I'm on cs50's lab #4 - "Volume". I believe I understand the solution other than the use of the buffer variable here:

// TODO: Read samples from input file and write updated data to output file
int16_t buffer;
while (fread(&buffer, sizeof(int16_t), 1, input))
{
    buffer *= factor;
    fwrite(&buffer, sizeof(int16_t), 1, output);
}

From what I think I understand from the lecture, I thought that initializing a variable this way would assign a memory location on the stack, which is a static address and is not able to be indexed into like an array or a pointer on the heap.

Why is fwrite able to keep writing to the address of the same variable which is declared outside of the scope of the while loop?

The way the solution is written looks like the loop is just updating and multiplying the value of the same memory address over and over until the EOF. I could understand if it was int16_t buffer[]; instead because you can index into an array, but how does this solution work without using malloc? How is this not causing a segmentation fault?


Solution

  • From what I think I understand from the lecture, I thought that initializing a variable this way would assign a memory location on the stack, which is a static address and is not able to be indexed into like an array or a pointer on the heap.

    Given ...

    int16_t buffer;
    

    ... the expression &buffer evaluates to the address of local variable buffer. The result can be used in all the same ways that any other address of an int16_t can be used.

    Division of memory into "stack" and "heap" is not a C concept, and C semantics do not depend on it. The most closely related C concepts are objects' storage duration and lifetime, but there is no differentiation between objects with different storage durations with respect to how they or their addresses may be used during their lifetimes.

    Your buffer has "automatic" storage duration. Its lifetime starts immediately after its declaration, and continues until the termination of the innermost block containing its declaration. That includes the complete execution of the while loop.

    Each time the while condition, fread(&buffer, sizeof(int16_t), 1, input) is evaluated, an attempt is made to read one input the size of an int16_t from file input into the object at address &buffer (that is, into buffer itself). That will succeed as long as there is at least that much data available to be read and no I/O error occurs. There is nothing whatever wrong with using buffer or &buffer that way.

    Why is fwrite able to keep writing to the address of the same variable which is declared outside of the scope of the while loop?

    Note that fwrite() is writing the data stored at that address -- that is, the data that were just read -- not writing the address itself. And why shouldn't it be able to do that?

    You do seem to have your concept of scope backwards, however. The while loop is inside the scope of the declaration of buffer, so buffer is accessible by name inside the loop body. A variable declared inside the loop body would not be accessible outside, but the reverse is not true (and I am confident that you have already seen lots of counterexamples).

    The way the solution is written looks like the loop is just updating and multiplying the value of the same memory address over and over until the EOF. I could understand if it was int16_t buffer[]; instead because you can index into an array, but how does this solution work without using malloc? How is this not causing a segmentation fault?

    The way the solution is written looks like the loop is just updating and multiplying the value of the same memory address over and over until the EOF.

    It is not modifying any memory addresses. It is reading data into the object (buffer) at a given address, then writing that data back out to the output. Over and over again until it has thereby copied all the data from the input to the output.

    I could understand if it was int16_t buffer[]; instead because you can index into an array,

    A pointer to a scalar object is equivalent to a pointer to an array of length 1.

    but how does this solution work without using malloc?

    You need pointers to use malloc(). You do not need malloc() to use pointers.

    How is this not causing a segmentation fault?

    There is simply no reason why it should. The address of an object is a valid pointer value during that object's lifetime. It can be used in all ways just like any other pointer of the same (pointer) type. The code presented does not attempt to write or read outside the bounds of the object (buffer) to which the particular pointer in question points. It's all A-ok.