volkan g
volkan g

Reputation: 296

How to initialize multiple arrays with unknown sizes via a function in C

I have a program in C, in which I initialize multiple number of arrays each with a bunch of lines. However, i'd like to avoid that since it increases the length of my main function. For example I have this;

int * pickup_Ind;
double *pickup_Val;
pickup_Ind = (int *) malloc(sizeof(int) * (size1));
pickup_Val = (double *) malloc(sizeof(double) * (size1));

int * lInd;
double *lVal;
lInd = (int *) malloc(sizeof(int) * size2);
lVal = (double *) malloc(sizeof(double) * size2);

int * simul_Ind;
double *simul_Val;
simul_Ind = (int *) malloc(sizeof(int) * (size3));
simul_Val = (double *) malloc(sizeof(double) * (size3));

I know I can reduce the number of lines by for example writing as:

int * pickup_Ind = (int *) malloc(sizeof(int) * (size1));

But still i will need to do this for every array. How to write this in a compact form with a function (which i will store in a header file), and then call this function from main. Not to mention i do not want to declare them as global variables, but to be able to use them in main. I tried the function below.

void initialize_bounds(int *arr1,int size1)
{
arr1= (int *) malloc(sizeof(int) * (size1));
for(int i=0;i<size1;i++)
    arr1[i]=i;  
}

But if i call this function via the following in main, i get error "Varuable test being used without initialized"

int* test;
initialize_bounds(test);

So to sum up, if i could write something like this, my problem is solved:

int *pickup_Ind,*pickup_Val,*lind,*lval;
int size1,size2;
initalize_bounds(pickup_Ind,pickup_Val,size1,size2);

Upvotes: 2

Views: 95

Answers (4)

I'd use something higher level, e.g. stretchy buffers. See this video for a live coding session that implements those - props to Per Vognsen for making this code, and for placing into public domain (i.e. completely free to use for any purpose, but I'm not a lawyer, so take anything I say with caution :).

You'd want to include bitwise/ion/common.c in your source file, and then the array allocation becomes simple. Stretchy buffers are perhaps the closest you get to the convenience of C++'s std::vector in C. They offer an API that doesn't feel like a C++ API transcribed in C - it is at the correct level, and lets you use plain pointers in a very sensible way (e.g. a buf_len of a NULL pointer is zero, not a crash, buf_push(mybuf, element) appends an element to the array and extends it if necessary, etc.

#include <assert.h>
#include <string.h>
#include <stdlib.h>
// note that common.c includes nothing, so you have to set it up
#include "common.c" 

#define buf_resize(b, n) ((n) <= buf_len(b) ? (b) : (((b) = buf__grow((b), (n), sizeof(*(b)), 0)), ((b) ? buf__hdr((b))->len = (n) : 0), (b)))

typedef struct {
  int * pickup_Ind;
  double *pickup_Val;
  int * lInd;
  double *lVal;
  int * simul_Ind;
  double *simul_Val;
} Data;

enum {
  size1 = ...,
  size2 = ...,
  size3 = ...
}

Data make_Data(void) {
  Data d;
  memset(&d, 0, sizeof(d));

  assert(buf_len(d->pickup_Ind) == 0);
  buf_resize(d.pickup_Ind, size1);
  buf_resize(d.pickup_Val, size1);
  buf_resize(d.lInd, size2);
  buf_resize(d.lVal, size2);
  buf_resize(d.simul_Ind, size3);
  buf_resize(d.simul_Val, size3);
}

int main(int argc, char **argv) {
  Data d = make_Data();
  assert(buf_len(d.pickup_Ind) == size1);
  d.pickup_Ind[0] = 10;
  assert(buf_len(d.pickup_Ind) == size1);
  buf_push(d.pickup_Ind, 11);
  assert(buf_len(d.pickup_Ind) == size1 + 1);
}

If you're building up the arrays by adding elements to them one-by-one, it'll make sense to reserve the capacity for the expected size of the array via buf_fit (it only reserves the memory but the buffer retains its length (e.g. zero)). The capacity reservation is entirely optional, though. It's there to prevent reallocation of the arrays while you add elements to them.

Thus:

Data make_Data(void) {
  Data d;
  memset(&d, 0, sizeof(d));

  assert(buf_len(d->pickup_Ind) == 0);
  buf_fit(d.pickup_Ind, size1);
  buf_fit(d.pickup_Val, size1);
  buf_fit(d.lInd, size2);
  buf_fit(d.lVal, size2);
  buf_fit(d.simul_Ind, size3);
  buf_fit(d.simul_Val, size3);
}

int main(int argc, char **argv) {
  Data d = make_Data();
  assert(buf_len(d.pickup_Ind) == 0); // zero length: no data in the array (yet!)
  assert(buf_cap(d.pickup_Ind) >= size1); // but it has the capacity we need
  buf_push(d.pickup_Ind, 10);
  buf_push(d.pickup_Ind, 11);
  assert(buf_len(d.pickup_ind) == 2);
}

If you'll want to use stretchy buffers in multiple source files, you'll run afoul of the one declaration rule (ODR). Thus, you'll need to factor out macro definitions and function declarations out of common.c and into common.h.

If the Data is only allocated once, there's no need to free it prior to exiting the program: the operating system already does it for you. Otherwise, you may wish to add a function to do this job:

void free_Data(Data *d) {
  buf_free(d.pickup_Ind);
  buf_free(d.pickup_Val);
  buf_free(d.lInd);
  buf_free(d.lVal);
  buf_free(d.simul_Ind);
  buf_free(d.simul_Val);
  assert(buf_len(d.pickup_Ind) == 0);
}

Upvotes: 0

JonatanE
JonatanE

Reputation: 941

You can write a function that returns a freshly allocated and initialized array.

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

/* Header file */
int* AllocateArray(size_t size);
void DeallocateArray(int *array);

int main(void) {
    const size_t size = 10;
    int *const array = AllocateArray(size);
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", array[i]);
    }
    printf("\n");

    DeallocateArray(array);
    return 0;
}

/* Implementation */
int* AllocateArray(size_t size) {
    int *const array = malloc(size * sizeof(int));
    if (array == NULL) {
        // Allocation failed, handle it...
    }

    for (size_t i = 0; i < size; ++i) {
        array[i] = i;
    }

    return array;
}

void DeallocateArray(int *array) {
    if (array == NULL) {
        return;
    }
    free(array);
}

Upvotes: 0

Ctx
Ctx

Reputation: 18420

You could write a function

void initialize_bounds(int **ind, double **val, int size) {
    *ind = malloc(sizeof (**ind)*size);
    for (int i = 0; i < size; i++) {
        (*ind)[i] = i;
    }
    *val = malloc(sizeof (**val)*size);
}

and call it like

int * pickup_Ind;
double *pickup_Val;
initialize_bounds(&pickup_Ind, &pickup_Val, size1);

to initialize both arrays in one line. You still have to place one call to it per array-pair, however.

Upvotes: 2

Adrian Mole
Adrian Mole

Reputation: 51845

In the C language, arguments are passed to functions by value - so, actually, a copy is made and the original variable (in the calling code) cannot be changed. So, if you want a function to modify (say) an int argument, you pass it a pointer to that int.

Likewise, if you want a function to modify a pointer, you have to pass a pointer to that pointer.

So, in the case of the initialize_bounds function you have shown, you would need this:

void initialize_bounds(int** arr1,int size1) // 1st arg is a pointer to the pointer!
{
    *arr1 = (int *) malloc(sizeof(int) * (size1)); // De-reference our `arr1` pointer
    for(int i=0;i<size1;i++)
        (*arr1)[i]=i;  
}

Then, you can use this to initialize a pointer in your main function with a call like this:

int* test;
initialize_bounds(&test); // We need to pass the ADDRESS of the pointer we want to modify!

Upvotes: 2

Related Questions