0

I have a while loop that exits when an if(condition){break;} is met, after an unknown number of iterations. Inside the while loop, a function is called, that will return an array of variable size at every iteration. I know in python I could just append the arrays one to each other, and in the end I would have an array of arrays of variable size. What is the right approach to do it in C?

Here is the relevant part of my code (I know it is not a MWE, many parts are missing, but I hope it is still understandable):

int find_zeros(double *kappa, double *zeros){

    // reset the counter "z"
    int z = 0;

    // do some calculations and compute the zeros
    // update the value of z for every zero found

    // "zeros" now contains "z" elements
    return z;
}

double *foo(){

    // allocate memory for the "zeros" array (I know that N>z)
    double *zeros = (double *) malloc (sizeof(double) *N);

    // variable to count the elements of the "zeros" array
    int z;

    while (1){

        z = find_zeros(kappa, zeros);

        // if no zeros where found, exit the while loop
        if (z==0){ break; }

        // now we know how many zeros we have (z has been updated 
        // by find_zeros()), so we can reallocate the memory
        zeros = (double *) realloc(zeros, sizeof(double) *z);

        // append the zeros somewhere (how?!)
        // in python I would do: zeros_list.append(zeros)
    }

    // free the memory for the "zeros" array
    free(zeros);

    // return all the zeros found 
    // in python I would do: return zeros_list
}

int main(){

    double *zeros_list = what?? 
    // how do I allocate memory of a thing that I don't
    // know how big it is going to be?

    zeros_list = foo();
}
Tropilio
  • 1,395
  • 1
  • 9
  • 27
  • 1
    The standard technique is to allocate a buffer and grow it (with `realloc`) as needed. – William Pursell Oct 21 '19 at 13:19
  • If you allocate memory in `find_zeros` and return the pointer to this memory, the caller does not know its size nor how many elements are actually used. Maybe you should return a structure that contains both `zeros ` and `z`. – Bodo Oct 21 '19 at 13:26
  • @Bodo I changed the code, now ``zeros`` is passed to the function ``find_zeros()`` as an argument and ``z`` is the output, do you think it is better? – Tropilio Oct 21 '19 at 13:38
  • If `N` is a global constant then this solution might be sufficient for a single call, but as you want to reallocate the buffer in the caller and call `find_zeros` in a loop, you should pass the address of the first free element and the number of free elements (if it is not the global `N`). – Bodo Oct 21 '19 at 13:43
  • 2
    just an fyi, malloc doesn't initialize the allocated space so there may be whatever inside. If you want to allocate and initialize the space you can use calloc instead, it workd the same as malloc but initializes the allocated space with all 0s. – John Doe Oct 21 '19 at 14:58
  • In C, when calling any of the heap allocation functions: `malloc()`, `calloc()` and/or `realloc()` 1) the returned type is `void *` which can be assigned to any pointer. Casting just clutters the code, making it more difficult to understand, debug, etc. 2) always check (!=NULL) the returned value to assure the operation was successful. – user3629249 Oct 22 '19 at 04:29
  • regarding: `zeros = (double *) realloc(zeros, sizeof(double) *z);` the function: `realloc()` can fail, in which case, the returned value is NULL. Assigning the returned value directly to the target pointer results in a memory leak (and the program will crash when an offset of the NULL pointer is de referenced.) Always assign to a temporary variable, check for a successful call to `realloc()` and IF successful, then assign the temporary variable to the target pointer – user3629249 Oct 22 '19 at 04:32

2 Answers2

2

You need to store store the zeros in foo independently from the values returned by find_zeros, as you would in Python, where you'd have separate variables zeros_list and zeros.

Python's list.append method is realized in two steps: first the array is reallocated to new capacity with realloc (you already have that part), and then the new value is assigned to the newly available space. Since you are creating an array of arrays, you also need to copy the values from their temporary location to the new one with memcpy(). For example (untested, in absence of a MWE):

struct floatvec {
    double *values;
    int length;
}

struct floatvec *foo(int *howmany) {
    struct floatvec *zeros_list = NULL;
    int zeros_cnt = 0;
    static double zeros[N];

    while (1) {
        int z = find_zeros(kappa, zeros);
        if (z == 0)
            break;

        struct floatvec new = {
            .values = malloc(z * sizeof(double)),
            .length = z
        };
        if (!new.values)
            return NULL;
        memcpy(new.values, zeros, sizeof(double) * z);

        zeros_list = realloc(zeros_list, sizeof(struct floatvec) * (zeros_cnt + 1));
        if (!zeros_list)
            return NULL;
        zeros_list[zeros_cnt++] = new;
    }

    *howmany = zeros_cnt;
    return zeros_list;
}

Usage example:

int cnt;
struct floatvec *all_zeros = foo(&cnt);
for (int i = 0; i < cnt; i++) {
    struct floatvec *curr = all_zeros[i];
    for (int j = 0; j < curr->length; j++)
        printf("%lf\n", curr->values[j]);
}

A couple of unrelated notes:

  • don't cast the result of malloc and realloc.
  • check whether malloc returned NULL and abort your program or make an error return to the caller.

EDIT: updated the answer to match the edited question.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Thanks for your answer! I tried implementing it and I am getting closer to a solution. However, I have two comments: 1) I don't really understand the use of ``howmany``, could you explain me what it does? 2) In this way all the outputs of ``find_zeros()`` will be "stacked" in a single row in ``zero_list``, while i would like them "stacked in rows". What I mean is I'd like the final ``zero_list`` array to contain ``n`` arrays, where ``n`` is the number of iterations of the while loop, and each of these ``n`` arrays will in turn contain the outputs of ``find_zeros()`` in the ``n``-th iteration. – Tropilio Oct 21 '19 at 15:47
  • Added a usage example and changed the code to return a jagged array of arrays. – user4815162342 Oct 21 '19 at 18:48
0

regarding your question:

// append the zeros somewhere (how?!)

the easiest way is a call to memcpy() similar to:

memcpy( &zeros[ last used offset ], newXeros, sizeof( newzeros ) );
user3629249
  • 16,402
  • 1
  • 16
  • 17