2

It may be that at times, for performance reasons, I may decide to define a struct such as

typedef struct {
    size_t capacity;
    size_t size;
    int offset;
    void* data[];
} ring_buffer;

inlining data in the struct itself. I'm currently then defining the creation function as

ring_buffer* ring_buffer_create(size_t capacity) {
    int struct_size = 
      sizeof(int) + 2 * sizeof(size_t) + 
      capacity * sizeof(void*);
    ring_buffer* rb = (ring_buffer*)malloc(struct_size);
    rb->capacity = capacity;
    rb->offset = 0;
    rb->size = 0;
    return rb;
}

which (if I didn't miscalculate anything) will work fine as long as the C compiler doesn't do some weird field padding / alignments.

How do people in general deal with this situation (other than defining data to be a pointer, of course)?

Thanks

devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • 1
    Please read [Why isn't sizeof for a struct equal to the sum of sizeof of each member?](https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member) Do e.g. `ring_buffer = malloc(sizeof(ring_buffer) + capacity * sizeof(void *))` – Some programmer dude Jul 18 '19 at 18:58
  • Ah, the idea with your code snippet is to overallocate by "a bit" to guarantee that when I access `rb->data` for a given size I'm always in a valid memory region? – devoured elysium Jul 18 '19 at 19:02
  • That's how [flexible array members](https://en.wikipedia.org/wiki/Flexible_array_member) work. It's part of the C language. The only other way is to use a pointer and have a separate allocation for it. – Some programmer dude Jul 18 '19 at 19:05
  • Cool, thx. Maybe move your comments to a full-fledged reply? – devoured elysium Jul 18 '19 at 19:08
  • The correct computation is `sizeof(ring_buffer) + capacity * sizeof(void*)`. Or `sizeof *rb + capacity * sizeof rb->data[0]`. (I'd use the second one on general principles.) – rici Jul 18 '19 at 21:22

1 Answers1

2

Your size calculation is incorrect: the structure could have embedded padding to ensure the alignment of some of its members. For example on 64-bit targets, int is 4 bytes and the void *data[] array that follows requires 8-byte alignment, so the compiler will insert 4 bytes of padding before the data member.

The size should be computed this way:

size_t struct_size = sizeof(ring_buffer) + capacity * sizeof(void *);

Or possibly:

size_t struct_size = offsetof(ring_buffer, data) + capacity * sizeof(void *);

Note that size should have type size_t and you should test for malloc failure to avoid undefined behavor:

ring_buffer *ring_buffer_create(size_t capacity) {
    size_t struct_size = sizeof(ring_buffer) + capacity * sizeof(void *);
    ring_buffer *rb = malloc(struct_size);
    if (rb) {
        rb->capacity = capacity;
        rb->offset = 0;
        rb->size = 0;
    }
    return rb;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189