1

I am writing a C program that take sox's output as input for my program. Generally, my program would read the input from stdin and make some processing afterward. However, when I read byte values from stdin and wrote it back to another file (just to make sure everything is correct), I saw that my result was somehow be delayed (I am not sure about this), comparing to the original one (image is here, the waveform above is the output of sox's command).

Can someone point out for me where do I go wrong please? I have been struggled with this issue for so many hours. I am using Ubuntu 20.04. Thanks! (In case you want my audio file, here it is)

Sox command to generate above waveform

cat arctic_a0010.wav | sox -t wav - -b 16 -e signed -t raw - > mid.raw

Command to generate below waveform

cat arctic_a0010.wav | sox -t wav - -b 16 -e signed -t raw - | ./test

My minimal test.c program

#include <stdio.h>
#include <stdlib.h>

void storeValue(short* arr, short* assignValue, long int startPt, long int numBlock) {
    for (long int i = 0; i < numBlock; i++) {
        arr[startPt + i] = assignValue[i];
    }
}

void readFromStdin(short* arr, long* curSize) {
    long r, n = 0;
    int BUFFER_SIZE = 1024;
    short buffer[BUFFER_SIZE];

    freopen(NULL, "rb", stdin);
    while (1) {
        r = fread(buffer, sizeof(short), BUFFER_SIZE, stdin);
        if (r <= 0) {
            break;
        }
        if (n + r > *curSize) {
            *curSize = n + r;
            arr = (short*)realloc(arr, (*curSize) * sizeof(short));
        }
        storeValue(arr, buffer, n, r);
        n = n + r;
    }
}

int main(int argc, char *argv[])
{
    // Read from stdin
    short* inputArray = (short*)malloc(sizeof(short));
    long InpSize = 1, currentIndex = 0;
    readFromStdin(inputArray, &InpSize);

    // Write to file
    FILE *out = fopen("test.raw", "wb");
    fwrite(inputArray, sizeof(short), InpSize, out);
}
JonnyJack
  • 109
  • 8
  • 1
    In `readFromStdin`, the code `arr = ...` = means nothing to the caller of `readFromStdin`. `inputArray` will be left dangling the moment a true reallocation happens, thrusting your program into the abyss of *undefined behavior* back in `main` once you try to dereference that thing. As an added dose of salt in the wound, you also end up with a memory leak, since `main` never knows about the memory changes in `readFromStdin`. – WhozCraig Feb 25 '22 at 16:37
  • @WhozCraig Sorry, I am a little slow to understand what you said. What do you mean by `arr = . . .` means nothing to the caller of `readFromStdin`? I thought when I pass a pointer to a function, its content will update when the reference update. Am I misunderstanding something? – JonnyJack Feb 25 '22 at 16:46
  • I mean it means nothing to the caller. `inputArray` in `main` is not affected in *any* way by that assignment. Function arguments are passed by value, and that includes the "value" of a pointer. Only by *dereferencing* a pointer can you drive data into where it *stores* what lies underneath. – WhozCraig Feb 25 '22 at 16:51
  • `realloc()` may give you new base address; you need to return it from `readFromStdin()` or send in the pointer address. – जलजनक Feb 25 '22 at 16:55
  • @WhozCraig Oh thanks, now I kind of understand it. The last time I used C was 2 years ago so I forgot some basics. Now I know that I need to change the parameter `short* arr`, is that correct? If that is, do I need to change that of `storeValue(...)` function also? – JonnyJack Feb 25 '22 at 17:07
  • Don't read into `buffer` and then copy the data. Just read directly into `arr` – William Pursell Feb 25 '22 at 17:07
  • @SparKot Oh is that so? I follow this [webpage](https://www.programiz.com/c-programming/c-dynamic-memory-allocation) and its example shows that `realloc()` does not change base address. – JonnyJack Feb 25 '22 at 17:09
  • 1
    @JonnyJack it doesn't always *have* to change the base on every call, and in many suballocator models it won't until a paragraph is reached, but that is all implementation-specific; not part of the standard. If `realloc` is successful you should code as if it will *always* change the base, and act accordingly. – WhozCraig Feb 25 '22 at 17:12

1 Answers1

1

C passes arguments by value. That includes pointer arguments. Like all by-value arguments, changing the value within a function scope means nothing to the caller. If you want to convey a change in value to the caller there are multiple ways to do it, the most common shown below:


Use That Otherwise-Worthless Return Value

Right now your function returns void (e.g. nothing). Change it to send the (possibly updated) result of changes to arr. Like this:

short *readFromStdin(short* arr, long* curSize) {
    long r, n = 0;
    int BUFFER_SIZE = 1024;
    short buffer[BUFFER_SIZE];

    freopen(NULL, "rb", stdin);
    while (1) {
        r = fread(buffer, sizeof(short), BUFFER_SIZE, stdin);
        if (r <= 0) {
            break;
        }
        if (n + r > *curSize) {
            *curSize = n + r;
            arr = realloc(arr, (*curSize) * sizeof *arr);
        }
        storeValue(arr, buffer, n, r);
        n = n + r;
    }

    return arr;
}

and in main :

inputArray = readFromStdin(inputArray, &InpSize);

Formal Pointer-To Argument an Pass By Address

If you want to change an actual argument, you need to remember that C is pass-by-value. Therefore, you need to rig the parameter to accept an address of the 'thing' you want to change, and pass that address as the formal argument at the call site. Even the most novice C programmer is familiar with a simple integer swap:

#include <stdio.h>

void swap_int(int *pa, int *pb)
{
    int tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

int main()
{
    int a = 1, b = 2;
    swap_int(&a,&b);
    printf("%d %d\n", a, b);
}

Note this changes the values stored in a and b in main because their addresses are used as arguments to pointer-type formal parameters, and dereferencing allows us to drive data into them thereafter.

Now, consider this:

void make_me_bigger(int *arr, int *size)
{
    *size += 10;
    arr = realloc(arr, *size * sizeof *arr);
}

There is no dereferencing going on with arr = It is no different than this

int foo(int x)
{
    x = 20; // only changes x locally
}

Calling foo from main

int main()
{
    int x = 10;
    foo(x);
    printf("%d\n", x); // still 10
}

If you want to pass something by "reference", the way to do it is to declare the formal parameter to be pointer-to-type (where 'type' is the underlying data type), and pass the address of your var, just like we did above in swap_int.

This is true, even when the parameters are already pointer types. In those cases, the formal parameters become pointer-to-pointer-to-type, and the arguments from the call site are the addresses of pointer variables

In other words, at long last:

void readFromStdin(short **arr, long* curSize) {
    long r, n = 0;
    int BUFFER_SIZE = 1024;
    short buffer[BUFFER_SIZE];

    freopen(NULL, "rb", stdin);
    while (1) {
        r = fread(buffer, sizeof(short), BUFFER_SIZE, stdin);
        if (r <= 0) {
            break;
        }
        if (n + r > *curSize) {
            *curSize = n + r;
            *arr = realloc(*arr, (*curSize) * sizeof **arr);
        }
        storeValue(*arr, buffer, n, r);
        n = n + r;
    }
}

The plethora of other things (not checking your realloc results, casting mallocs, etc.) are all fuel for additional fixes, but the main problem can be addressed in either of the above techniques.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • Thank you so much for your detail answer. +1 upvote Btw, how do all of this cause my waveform generated by that code a little "delay" when comparing to the original one (as I mention in the question)? – JonnyJack Feb 25 '22 at 17:21