1

I'm creating a C-library with .h and .c files for a ring buffer. Ideally, you would initialize this ring buffer library in the main project with something like ringbuff_init(int buff_size); and the size that is sent, will be the size of the buffer. How can I do this when arrays in C needs to be initialized statically?

I have tried some dynamically allocating of arrays already, I did not get it to work. Surely this task is possible somehow?

What I would like to do is something like this:

int buffSize[];

int main(void)
{
    ringbuffer_init(100);      // initialize buffer size to 100
}

void ringbuffer_init(int buff_size)
{ 
    buffSize[buff_size];
}

This obviously doesn't compile because the array should have been initialized at the declaration. So my question is really, when you make a library for something like a buffer, how can you initialize it in the main program (so that in the .h/.c files of the buffer library) the buffer size is set to the wanted size?

SirDarius
  • 41,440
  • 8
  • 86
  • 100
C. K.
  • 71
  • 1
  • 11

4 Answers4

2

You want to use dynamic memory allocation. A direct translation of your initial attempt would look like this:

size_t buffSize;
int * buffer;

int main(void)
{
    ringbuffer_init(100);      // initialize buffer size to 100
}

void ringbuffer_init(size_t buff_size)
{ 
    buffSize = buff_size;
    buffer = malloc(buff_size * sizeof(int));
}

This solution here is however extremely bad. Let me list the problems here:

  1. There is no check of the result of malloc. It could return NULL if the allocation fails.
  2. Buffer size needs to be stored along with the buffer, otherwise there's no way to know its size from your library code. It isn't exactly clean to keep these global variables around.
  3. Speaking of which, these global variables are absolutely not thread-safe. If several threads call functions of your library, results are inpredictible. You might want to store your buffer and its size in a struct that would be returned from your init function.
  4. Nothing keeps you from calling the init function several times in a row, meaning that the buffer pointer will be overwritten each time, causing memory leaks.
  5. Allocated memory must be eventually freed using the free function.

In conclusion, you need to think very carefully about the API you expose in your library, and the implementation while not extremely complicated, will not be trivial.

Something more correct would look like:

typedef struct {
    size_t buffSize;
    int * buffer;
} RingBuffer;

int ringbuffer_init(size_t buff_size, RingBuffer * buf)
{
    if (buf == NULL)
        return 0;

    buf.buffSize = buff_size;
    buf.buffer = malloc(buff_size * sizeof(int));

    return buf.buffer != NULL;
}

void ringbuffer_free(RingBuffer * buf)
{
    free(buf.buffer);
}

int main(void)
{
    RingBuffer buf;
    int ok = ringbuffer_init(100, &buf);      // initialize buffer size to 100

    // ...

    ringbuffer_free(&buf);
}

Even this is not without problems, as there is still a potential memory leak if the init function is called several times for the same buffer, and the client of your library must not forget to call the free function.

SirDarius
  • 41,440
  • 8
  • 86
  • 100
1

Static/global arrays can't have dynamic sizes.

If you must have a global dynamic array, declare a global pointer instead and initialize it with a malloc/calloc/realloc call.

You might want to also store its size in an accompanying integer variable as sizeof applied to a pointer won't give you the size of the block the pointer might be pointing to.

int *buffer;
int buffer_nelems;

char *ringbuffer_init(int buff_size)
{ 
    assert(buff_size > 0);
    if ( (buffer = malloc(buff_size*sizeof(*buffer)) ) )
        buffer_nelems = buff_size;
    return buffer;
}
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
0

You should use malloc function for a dynamic memory allocation.

It is used to dynamically allocate a single large block of memory with the specified size. It returns a pointer of type void which can be cast into a pointer of any form.

Example:

// Dynamically allocate memory using malloc() 
buffSize= (int*)malloc(n * sizeof(int));

// Initialize the elements of the array
for (i = 0; i < n; ++i) { 
    buffSize[i] = i + 1; 
} 

// Print the elements of the array 
for (i = 0; i < n; ++i) { 
    printf("%d, ", buffSize[i]); 
} 
Doug Richardson
  • 10,483
  • 6
  • 51
  • 77
  • You do not need to cast `malloc` - and you shouldn't - See https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – Ed Heal Aug 09 '19 at 22:49
0

I know I'm three years late to the party, but I feel I have an acceptable solution without using dynamic allocation.

If you need to do this without dynamic allocation for whatever reason (I have a similar issue in an embedded environment, and would like to avoid it).

You can do the following:

Library:

int * buffSize;
int buffSizeLength;

void ringbuffer_init(int buff_size, int * bufferAddress)
{
buffSize = bufferAddress;
buffSizeLength = buff_size;
}

Main :


#define BUFFER_SIZE 100
int LibraryBuffer[BUFFER_SIZE];

int main(void)
{
    ringbuffer_init(BUFFER_SIZE, LibraryBuffer )     // initialize buffer size to 100
}

I have been using this trick for a while now, and it's greatly simplified some parts of working with a library.

One drawback: you can technically mess with the variable in your own code, breaking the library. I don't have a solution to that yet. If anyone has a solution to that I would love to here it. Basically good discipline is required for now.

You can also combine this with @SirDarius 's typedef for ring buffer above. I would in fact recommend it.