2

The following code fails to compile because the compiler complains about char** being incompatible with const char* const* in the call to PrintStrings(). If I declare the strs variable as const char**, then the call to PrintStrings() works, but the memcpy() and free() calls then complain that they are getting const types. Is there anyway around this issue? Note that I don't want to cast strs to the incompatbiele type const char* const* in the call to PrintStrings(), because I'm using very aggressive compiler optimizations that rely on never breaking aliasing rules.

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

void
PrintStrings(const char* const* strs)
{
    int x = 0;
    while(strs[x])
        printf("%s", strs[x++]);
}

int
main(void)
{
    int x = 0;

    /* If I make this "const char**" then the memcpy's and free's will fail,
     * but if I leave the const off then the call to PrintString() fails */

    char** strs = malloc(5 * sizeof(char*));

    strs[0] = malloc(128);
    memcpy(strs[0], "Hello", 1 + strlen("Hello"));

    strs[1] = malloc(128);
    memcpy(strs[1], " ", 1 + strlen(" "));

    strs[2] = malloc(128);
    memcpy(strs[2], "World!", 1 + strlen("World!"));

    strs[3] = malloc(128);
    memcpy(strs[3], "\n", 1 + strlen("\n"));

    strs[4] = NULL;

    PrintStrings(strs);

    while(strs[x])
        free(strs[x++]);
    free(strs);

    return 0;
}

[EDIT]

Please remove the duplicate marker from this question. I fully understand why the cast is invalid, and unlike the other poster that's not what I'm asking about. It's true that both my question and that other question center around the same compiler issue, but the other question is asking about why the compiler does that in the first place, whereas I'm asking for a workaround in a very specific and tricky case.

fieldtensor
  • 3,972
  • 4
  • 27
  • 43
  • You want `const_cast`. I'm not sure what you mean by "breaking aliasing rules". When you pass a pointer (`const` or otherwise) to a function, either the optimizer can see the body of the function and prove that it doesn't do anything nefarious, or it has to assume the function stashes away a copy of the pointer, making aliasing possible. `const_cast` doesn't change any of that. – Igor Tandetnik Jul 27 '14 at 22:54
  • 1
    Btw, be careful, you must do `malloc(5*sizeof(char*))`. – Ludo6431 Jul 27 '14 at 22:56
  • Thanks for the inputs. I updated the malloc to fix the type-o, and remove the C++ tag from the post's tags, because I'm working strictly in C. – fieldtensor Jul 27 '14 at 22:57
  • There's no such thing as `const char* const*`, right? Should be `const char ** const`? – Fiddling Bits Jul 27 '14 at 23:01
  • 2
    I don't think it's a duplicate as he's not asking why, but how to work around the issue. – Ross Ridge Jul 27 '14 at 23:25
  • The simplest workaround is `PrintStrings( (char const *const *)strs );`. This does not violate any aliasing rules, it is always permitted to alias a type as the const or non-const version of that type. – M.M Jul 27 '14 at 23:28
  • @FiddlingBits no, any level of indirection can be const or non-const (or volatile etc.) – M.M Jul 27 '14 at 23:29

1 Answers1

2

Change your code so it's like this:

char const **strs = malloc(sizeof(char*) * 5);
char *s;

strs[0] = s = malloc(128);
memcpy(s, "Hello", 1 + strlen("Hello"));

strs[1] = s = malloc(128);
memcpy(s, " ", 1 + strlen(" "));

...

while(strs[x])
    free((char *) strs[x++]);
Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • How would I handle the free() calls then? I think I would have to cache each value of "s" as I go? That would actually work, cool. – fieldtensor Jul 27 '14 at 23:02
  • You could, but I'd just cast them to `char *` or `void *`. There aren't any aliasing issues to worry about with `free`. – Ross Ridge Jul 27 '14 at 23:07
  • Expanding my last comment, out of context something like `PrintStrings((char const * const *) strs);` looks suspicious because its correctness depends on what type `strs` is. So I would rather not have code that used a cast like that. On the other hand, a statement like `free((char *)strs[0])` is much less worrisome because all that matters is that `strs[0]` was allocated with `malloc`. – Ross Ridge Jul 27 '14 at 23:21
  • That's a really good point. I think that solution is the best. Marking the question as answered – fieldtensor Jul 28 '14 at 01:15
  • If you ask the compiler to be "picky" (eg. `-Wcast-qual` with gcc), it will whine about casting away the `const` when you come to free the thing -- which is entirely correct, from its perspective. Of course, there's only so much the compiler can do... a double cast will do the trick: `(void*)(uintptr_t)strs[x++]` (ho ho) –  Jul 28 '14 at 08:59