0

I have an issue with memcpy and valgrind, telling me about an Invalid write of size 8. I got to the point of figuring out where the faulty code is, but I have no clue as to why it is faulty... I'm aware that there are other questions regarding that, but they don't help me really.

The following is an excerpt of the most important bits of my approach on a somewhat "universal" stack, when my regular value would be of type uintptr_t.

Here are two defines that I used below:

// default stack batch size
#define STACK_BATCH_DEFAULT 8
// size of one value in the stack
#define STACK_SIZEOF_ONE    sizeof(uintptr_t)

The structure of the stack is as follows:

typedef struct Stack
{
    size_t count;       // count of values in the stack
    size_t size;        // size of one value in bytes
    size_t alloced;     // allocated count
    uintptr_t *value;   // the values
    int batch;          // memory gets allocated in those batches
}
Stack;

I have an initialization function for the stack:

bool stack_init(Stack *stack, size_t size, int batch)
{
    if(!stack) return false;
    stack->batch = batch ? batch : STACK_BATCH_DEFAULT;
    stack->size = size;
    stack->count = 0;
    stack->value = 0;
    stack->alloced = 0;
    return true;
}

Then the stack_push function, where valgrind throws the error Invalid write of size 8:

bool stack_push(Stack *stack, uintptr_t *value)
{
    if(!stack || !value) return false;
    // calculate required amount of elements
    size_t required = stack->batch * (stack->count / stack->batch + 1);
    // allocate more memory if we need to
    if(required > stack->alloced)
    {
        uintptr_t *tmp = realloc(stack->value, required * stack->size);
        if(!tmp) return false;
        stack->value = tmp;
        stack->alloced = required;
    }
    // set the value
    if(stack->size > STACK_SIZEOF_ONE)
    {
        memcpy(stack->value + stack->size * stack->count, value, stack->size);  // <--- valgrind throws the error here
    }
    else
    {
        stack->value[stack->count] = *value;
    }
    // increment count
    stack->count++;
    return true;
}

Then in my program I'm calling the functions as follows:

Stack stack = {0};  
stack_init(&stack, sizeof(SomeStruct), 0);
/* ... */
SomeStruct push = { // this is a struct that is larger than STACK_SIZEOF_ONE
    .int_a = 0,
    .int_b = 0,
    .int_c = 0,
    .id = 0,
    .pt = pointer_to_struct,    // it is a pointer to some other struct that was allocated beforehand
};
stack_push(&stack, (uintptr_t *)&push);

And with universal I meant that I can also have a regular stack:

Stack stack = {0};
stack_init(&stack, sizeof(uintptr_t), 0);
/* ... */
uintptr_t a = 100;
stack_push(&stack, &a);

Also, I'm open to hear general tips and advices if there are any things that should/could be improved :)

Edit: Below is a runnable code.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

// default stack batch size
#define STACK_BATCH_DEFAULT 8
// size of one value in the stack
#define STACK_SIZEOF_ONE    sizeof(uintptr_t)

#define TESTCOUNT   10
#define MAX_BUF     16

typedef struct Stack
{
    size_t count;       // count of values in the stack
    size_t size;        // size of one value in bytes
    size_t alloced;     // allocated count
    uintptr_t *value;   // the values
    int batch;          // memory gets allocated in those batches
}
Stack;

typedef struct SomeStruct
{
    size_t a;
    size_t b;
    size_t c;
    size_t id;
    char *str;
}
SomeStruct;

bool stack_init(Stack *stack, size_t size, int batch)
{
    if(!stack) return false;
    stack->batch = batch ? batch : STACK_BATCH_DEFAULT;
    stack->size = size;
    stack->count = 0;
    stack->value = 0;
    stack->alloced = 0;
    return true;
}

bool stack_push(Stack *stack, uintptr_t *value)
{
    if(!stack || !value) return false;
    // calculate required amount of elements
    size_t required = stack->batch * (stack->count / stack->batch + 1);
    // allocate more memory if we need to
    if(required > stack->alloced)
    {
        uintptr_t *tmp = realloc(stack->value, required * stack->size);
        if(!tmp) return false;
        stack->value = tmp;
        stack->alloced = required;
    }
    // set the value
    if(stack->size > STACK_SIZEOF_ONE)
    {
        memcpy(stack->value + stack->size * stack->count, value, stack->size);  // <--- valgrind throws the error here
    }
    else
    {
        stack->value[stack->count] = *value;
    }
    // increment count
    stack->count++;
    return true;
}

bool stack_pop(Stack *stack, uintptr_t *value)
{
    if(!stack) return false;
    if(!stack->count) return false;

    // decrement count of elements
    stack->count--;

    // return the value if we have an address
    if(value)
    {
        if(stack->size > STACK_SIZEOF_ONE)
        {
            memcpy(value, stack->value + stack->size * stack->count, stack->size);
        }
        else
        {
            *value = stack->value[stack->count];
        }
    }
    
    int required = stack->batch * (stack->count / stack->batch + 1);
    if(required < stack->alloced)
    {
        uintptr_t *tmp = realloc(stack->value, required * stack->size);
        if(!tmp) return false;
        stack->value = tmp;
        stack->alloced = required;
    }
    if(!stack->value) return false;

    return true;
}

int main(void)
{
    // initialize variables
    bool valid = false;
    Stack default_stack = {0};
    Stack some_stack = {0};

    // initialize stacks
    stack_init(&default_stack, sizeof(uintptr_t), 0);
    stack_init(&some_stack, sizeof(SomeStruct), 0);

    // test default case - push
    printf("Testing the default case, pushing...\n");
    for(int i = 0; i < TESTCOUNT; i++)
    {
        uintptr_t push = i;
        valid = stack_push(&default_stack, &push);
        if(!valid) return -1;
    }
    // ...now pop
    printf("Testing the default case, popping...\n");
    do
    {
        uintptr_t pop = 0;
        valid = stack_pop(&default_stack, &pop);
        if(valid) printf("%llu,", pop);
    }
    while(valid);
    printf("\n");

    // test some case - push
    printf("Testing some case, pushing...\n");
    for(int i = 0; i < TESTCOUNT; i++)
    {
        // generate the push struct
        SomeStruct push = {
            .a = i * 10,
            .b = i * 100,
            .c = i * 1000,
            .id = i,
            .str = 0,
        };
        // allocate a string
        push.str = malloc(MAX_BUF + 1);
        snprintf(push.str, MAX_BUF, "%d", i);
        // push
        valid = stack_push(&some_stack, (uintptr_t *)&push);
        if(!valid) return -1;
    }
    // ...now pop
    printf("Testing some case, popping...\n");
    do
    {
        SomeStruct pop = {0};
        valid = stack_pop(&some_stack, (uintptr_t *)&pop);
        if(valid)
        {
            printf("a=%d,b=%d,c=%d,id=%d,str=%s\n", pop.a, pop.b, pop.c, pop.id, pop.str);
            free(pop.str);
        }
    }
    while(valid);
    printf("\n");

    /* leave out free functions for this example.... */

    return 0;
}
rphii
  • 209
  • 2
  • 13
  • 4
    Seem to be way too complicated for a `push` function. – Eugene Sh. Nov 12 '21 at 15:12
  • @EugeneSh. this is not commercial code. It's for my spare time project and I'm a guy that likes universal things so this was my approach on it, check my edit to see why I made it how I made it.... Thanks for the note though! – rphii Nov 12 '21 at 15:17
  • 3
    Unrelated to being commercial or universal. It is just overcomplicated for the simple functionality it has to do. To your problem - why are you copying the whole `stack->size` from the `value` pointer, which is pointing to a single `uintptr_t` value? – Eugene Sh. Nov 12 '21 at 15:25
  • 1
    The destination address calculation is a mystery for me as well. – Eugene Sh. Nov 12 '21 at 15:32
  • @EugeneSh. In the case that you mean, I am only assigning it though...? The `memcpy` is used when I have a stack of a structure whose actual fields I don't know AND is larger than `sizeof(uintptr_t)`. In that case I thought that I could/should copy the values, if I want to have a `push` function that doesn't know the actual fields of that structure. – rphii Nov 12 '21 at 15:33
  • @rphii could you [edit] post a [mcve]? Something we can copy/paste/compile without the need to stich everything together. – Jabberwocky Nov 12 '21 at 15:35
  • I'll try, even though I thought that I already reduced it by alot so I'll add some parts back. I'll notify you when I did that... – rphii Nov 12 '21 at 15:37
  • @Jabberwocky I added a reproducible example – rphii Nov 12 '21 at 16:01
  • Why do you use _pointers_ to `uintptr_t` to represent values? – KamilCuk Nov 12 '21 at 16:19
  • I think that it's similar to when you pass an `int*` to a function to modify the value pointed to it. Since the dereferenced type of `uintptr_t*` is `uintptr_t` (still a pointer or integer) I should be able to modify contents pointed to it – rphii Nov 12 '21 at 17:00

1 Answers1

0

After hours I figured it out :D The mistake happened because I very rarely do pointer arithmetic... In short, I was assuming that it would always calculate with a byte.

Let's take a look at the lines containing:

memcpy(stack->value + stack->size * stack->count, value, stack->size);

...and break it down, so it is more readable. And also, I'll even add a handy dandy comment in it:

size_t offset = stack->size * stack->count; // offset in bytes
void *dest = stack->value + offset;
void *src = value;
memcpy(dest, src, stack->size);

Now the pro C-programmer should instantly spot the problem. It is with the calculation of stack->value + offset, where it should add offset in bytes but it is not, because the stack->value is of type uintptr_t * and not of type uint8_t *.

So to fix it, I replaced it with this line:

void *dest = (uint8_t *)stack->value + offset;

And the code works.

rphii
  • 209
  • 2
  • 13