0

Is it good practice to access a pointer variable by dereferencing a pointer to a pointer, which points to a different type or void? Could this break strict aliasing rules? C and C++ have some differences in aliasing rules. In this question we focus on C. The other question considering C++ can be found here. In the following example a double* is accessed as a void*.

int create_buffer(void** ptr, ...)
{
    *ptr = malloc(...);
    ...
}

int main(void)
{
    double* buffer;

    // The problematic code is here, double**
    // is coerced to void**, which is later
    // dereferenced by the function
    create_buffer((void**)&buffer, ...);
    ...
}

Is the following any better:

// keeping void** just as an indicator in the interface
// that the pointer is pointing to a pointer to any type
// it could be replaced by just void*
int create_buffer(void** ptr, ...)
{
    void* result = malloc(...);
    memcpy((void*)ptr, &result, sizeof result);
}
cmdLP
  • 1,658
  • 9
  • 19

2 Answers2

0

Not answering your question, but you could manoeuvrer around the uncertainties you mention by just doing well defined stuff like:

int main(void)
{
  double* buffer;

  {
    void * pv;
    create_buffer(&pv, ...);
    buffer = pv; /* C does not need any cast here. */
  }

  ...
alk
  • 69,737
  • 10
  • 105
  • 255
  • Or I could just change the `void**` parameter to a `void*` parameter and memcpy from/to the pointer. This shouldn't cause aliasing problems, but it hides the semantics of a pointer to pointer in the function signature. – cmdLP May 18 '19 at 15:18
  • @cmdLP: ... or you could just return `malloc()`'s result as the function's return value and return `NULL` on error. – alk May 18 '19 at 15:24
0

I would rather write it like this, assuming you use the "int" to return some useful information to the caller that it can't do without:

void *create_buffer(size_t n, int *otherInfo) {
    void *ptr = malloc(n);
    *otherInfo = 0;
    if (ptr) {
        // Do whatever other initialization is expected
        // memset(ptr, 0, n);
    } else {
        *otherInfo = -1;
    }
    return ptr;
}

int main(void)
{
    double* buffer;
    int code;

    /* cast is not required by language or compiler, 
       but I still find it useful when reading */
    buffer = (double *)create_buffer(SIZE_REQUIRED, &code);

    /* equality testing can be done either way. This way here,
       if you ever forget a =, will never compile anywhere. Nowadays
       many compilers are smart enough to catch it on their own anyway */
    if (NULL == buffer) {
        // Handle out of memory error or other problem
        switch(code) {
            case -1:
                fprintf(stderr, "Out of memory\n");
            ...
            default:
                fprintf(stderr, "Unexpected error %d\n", code);            
                break;
        }
    }
}
LSerni
  • 55,617
  • 10
  • 65
  • 107
  • 1
    No cast from `void*` necessary in C. Also `1234` does not make sense at all. – alk May 18 '19 at 16:07
  • The `ptr` parameter might also be an input parameter, which it is readen from and written to. This is why I chose the pointer parameter in the first place. The `int` return value is the indicator for success, the pointer is tested against `NULL` after malloc inside the function, but the pointer could also be `NULL` on success, eg. a buffer size of 0. – cmdLP May 18 '19 at 16:14
  • @alk I have added some explanations. As for 1234, it is my way of writing 'whatever', but yours is a good point nonetheless. Thanks – LSerni May 18 '19 at 18:09
  • @cmdLP a size zero buffer request shouldn't be issued in the first place in my opinion, or raise an error for the same reason. Also, then you should also test the pointer to be NULL **before** `malloc()`, otherwise you're at risk of leaking already-allocated memory; and you can't `free()` the pointer because in the funcion *you don't know* whether it points to allocated memory or is just uninitialized garbage. You *can* do all of that, of a certainty; but it doesn't look like a good practice to me. Mind you, it might well be that I've grown over-defensive over time :-) – LSerni May 18 '19 at 18:15