2

I compile some code with visual studio 9.0 (2008).

The behave as expected, but when I allocate some 2D array with some hand made functions, Visual-Studio generates some C4133 warning:

void ** alloc_2d(int w, int h, size_t type_size);
void free_d2(void ** mem);

int main (void)
{        
    float ** data;

    /* Here is generated a C4133 warning:
       "incompatible type from void ** to float **" */
    data = alloc_2d(100, 100, sizeof **data);

    /* do things with data */

    /* free data */
    free_2d(data);

    return 0;
}

I understand why this warning is generated, but I wonder what I should do to make it quiet.

What should I do?

  • Live with the warning?
  • Disable the warning (dangerous I think)?
  • Disable the warning around the alloc_2d calls (with some macros specific to visual studio)?
  • cast the function return (But [Do I cast the result of malloc?)

Second question:

  • Are newer/other compilers aware of this kind of cast?

Behind the void** are hidden two arrays: one big to store all data I need to be contiguous, and one other to browse through differents lines.

The implementation looks like (I removed the error checking)

void **alloc_2D_array(int w, int h, size_t size)
{
    void ** mem = malloc(w * sizeof *mem);
    *mem = malloc(w*h*size);
    for (i = 1; i < w; ++i)
    {
        mem[i] = (void*)((char*)mem[0] + i*w*size);
    }
    return mem;
}
Mathieu
  • 8,840
  • 7
  • 32
  • 45
  • 4
    `malloc` returns a `void *`, which is a generic pointer. But your function returns a `void **`, which is not a generic pointer, so you have to cast it. – mch Sep 06 '17 at 08:49
  • Why do you use a pointer of pointer? You may allocate a 2d array using a pointer and simple math computation! How have you written the alloc_2d function? (post it could be intersting to solve your problem). – Sir Jo Black Sep 06 '17 at 08:50
  • I think that it's better to avoid pointers of pointer if they're not strictly usefull. – Sir Jo Black Sep 06 '17 at 08:51

2 Answers2

3

From the signature, I assume your function is implemented roughly like this (plus error checking I'm leaving out here for brevity):

void **alloc_2d(int w, int h, size_t type_size)
{
    void **pointers = malloc(h * sizeof *pointers);
    for (size_t i = 0; i < h; ++i)
    {
        pointers[i] = malloc(w * type_size);
    }
    return pointers;
}

Some remarks on this:

  • The result of this function is not a 2d array, but an array of pointers to arrays. It can be used somewhat similar to a real 2d array, but comes with some overhead. A real 2d array would be one contiguous block of size w * h * type_size.
  • This code is in fact unsafe and could invoke undefined behavior when you cast the void ** returned from it to float **. This is because there's no guarantee that pointers to different types have the same representation -- they could even have different sizes. See also Are there any platforms where pointers to different types have different sizes?. By casting void ** to float **, you're treating an array of void * as if it was and array of float *. Although probably fine on your typical modern PC platform, this can go completely wrong.

That said, you could avoid the cast by simply having your function return void * instead of void **, but this would only hide the problem: void * is the generic pointer type and the compiler will allow implicit conversion to any other pointer type, but you'd still access your void * array using the wrong pointer type that way.


I suggest allocating a flat array instead and calculate the offsets manually, like this:

size_t rows = 100;
size_t cols = 100;
float *data = malloc(cols * rows * sizeof *data);

data[cols*5 + 3] = 1.41;
// ...

free(data);

As an alternative, you can use variable-length arrays (obligatory in C99, optional but almost always supported in C11 -- maybe not supported by Microsoft ....) to dynamically allocate a real 2d array:

size_t rows = 100;
size_t cols = 100;
float (*data)[cols] = malloc(rows * sizeof *data);

data[5][3] = 1.41;
// ...
free(data);
2

The generic pointer type in C is void*. That does not mean that void** is also a generic pointer type, the rule does not apply recursively.

Rather, a conversion from void** to float** or the other way around is an invalid pointer conversion. They are not compatible types - the compiler must issue a diagnostic message. Ignoring the warning might result in misaligned data or strict aliasing issues - in either case bugs.


What should I do?

Fix the code so that it doesn't contain forbidden pointer conversions.


Are newer/other compilers aware of this kind of cast?

This particular rule has not changed since the first standardization of C.


As for how you should fix your code... you shouldn't. You should rewrite it from scratch so that it allocates arrays and not "pointer-based look-up tables". There is no obvious reason why you would benefit from such a look-up-table here, but many reasons why you should avoid it.

In addition, your code would be much more readable if you use pointers to VLA.

See Correctly allocating multi-dimensional arrays for examples of how to do this proper (code example at the very bottom of the answer).

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • (Note that you might have to switch to a standard-compliant C compiler for this to work.) – Lundin Sep 06 '17 at 11:49
  • Fully agreed on using just one array if possible, but for the VLA you suggest, "*standard compliant*" isn't enough. MSVC is at least compliant to C89, but even if you strictly require compliance with the *latest* standard, VLAs are still *optional*. Therefore it's good to know how to achieve the same without them. –  Sep 06 '17 at 12:38