1
const void *a = something;
void *b = a;

returns a warning:

warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]

Is it safe (well defined behaviour) to copy a pointer to const to a pointer to non const via memcpy in order to avoid warnings?

/* Linear search */
void *vector_lsearch(const void *key, const void *base, int (*comp)(const void *, const void *))
{
    const struct vector *vector = CONST_VECTOR(base);
    void *cast[1];
    void *data;

    /* Skip const to non const warning */
    data = *(void **)memcpy(cast, &base, sizeof base);

    for (size_t item = 0; item < vector->size; item++)
    {
        if (comp(data, key) == 0)
        {
            return data;
        }
        data = (unsigned char *)data + vector->szof;
    }
    return NULL;
}
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • why not simply typecast? Warning tells you that you may not want to do that and by typecasting you depict you intention to do that. `memcpy()` shouldn't make any difference except that it would be less clear to another reader why u did that. – Mihir Luthra Oct 07 '19 at 05:57
  • 1
    It is as safe as ignoring the warning. – Marian Oct 07 '19 at 05:57
  • 1
    Those are not const pointers, they are pointers to const. Why do you need that conversion at all? – Mat Oct 07 '19 at 06:00
  • 1
    While it could be safe to ignore the warning sometimes, in almost all cases silencing such a warning by casting from constant data to non-constant data should be a red flag of something bad in either your implementation or your design. – Some programmer dude Oct 07 '19 at 06:05
  • @Mat: in order to do a linear search over a `const` array:, i.e. `const int arr[] = {1, 2, 3};`, as [bsearch](https://www.tutorialspoint.com/c_standard_library/c_function_bsearch.htm) does. – David Ranieri Oct 07 '19 at 06:05
  • 1
    You don't need a const conversion to do that. You'd need one if you wanted to modify the elements of the array (which would be undefined behavior in this case, the whole reason for the warning). With `const int *a;`, `a` is modifiable (a++ is valid), `*a` is not. – Mat Oct 07 '19 at 06:09
  • @Mat, I know, but passing the array as in this example: https://ideone.com/UdwbJo returns a: _warning: passing argument 1 of ‘func’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]_ – David Ranieri Oct 07 '19 at 06:14
  • Ignoring, casting or `memcpy`ing all are unsafe in respect that you possibly refer to immutable data (e. g. string literals) as modifiable. Not really a problem *as long as* you do *not* modify the data. But then why not work with pointers to const internally, too, and return a `void const` pointer? That's the safest option. It's the *user* of your function having to know if he can cast the result back to non-const, but she/he knows, too, what the function was called with... – Aconcagua Oct 07 '19 at 06:15
  • @DavidRanieri If the function you are passing the pointer to does not intend to modify the data, then make the pointer argument const in the function signature. If the function *should* modify the data... well then you shouldn't be passing in a pointer to const data. – Christian Gibbons Oct 07 '19 at 06:22
  • @Jabberwocky because this is most probably C, not C++. – Marco Bonelli Oct 07 '19 at 06:49
  • @Aconcagua, good point, but this is part of an [implementation of a vector](https://github.com/davranfor/c/blob/master/vector/vector.c), internally, the contents of the vectors must be mutable. – David Ranieri Oct 07 '19 at 07:10
  • @DavidRanieri I don't agree: `void* v = vector_create(...); void const* cv = v; someCallbackFunction(cv);` – for whatever reason, I do not want the user providing the callback to modify the vector. But the linear search is 'stealing' my const away, user won't even get a warning if trying to modify the data. So if you pass in const data, output should remain const as well, if referring to that const data. – Aconcagua Oct 07 '19 at 09:15

3 Answers3

3

It's safe to copy the pointer. The potential safety problem is when you use b. Since it's declared as a pointer to non-constant data, you can assign through the pointer, e.g. *(int *b) = 1; If something is constant data, this will cause undefined behavior.

If you're using the void * pointer as a conduit that will eventually pass the pointer to a function that will convert the pointer back to its original type (like the way qsort() uses its pointer argument), you should be able to ignore this warning. You would expect that function to cast it back to a pointer to const and not try to assign through it.

I don't think there's a way to declare a generic pointer that can be used as a conduit for either const or non-const data. If you declare it non-const, you'll get a warning when you assign a const pointer to it; if you declare it const, you won't be able to use it for functions that want a non-const pointer.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • `void *b = a;` isn't even valid C, it is not "just a warning", it is a language violation. – Lundin Oct 07 '19 at 08:38
  • @Lundin Is it a compiler error that he just gets a warning? – Barmar Oct 07 '19 at 14:40
  • The C standard only mentions "diagnostic messages". So the compiler is compliant as long as it informed the programmer in some way, be it through a warning, error or by a handwritten letter delivered with carrier pigeon :) My point is that "It's safe to copy the pointer" isn't really correct given the OP's code. It should rather say "It is impossible to copy the pointer". That the compiler produced a binary despite the warning, and what that binary is guaranteed to do, is another story. – Lundin Oct 07 '19 at 14:49
2

That warning comes from dropping the const qualifier as a part of initialization; just adding an explicit cast would also avoid the warning.

const void *a = something;
void *b = (void *)a;

Section 6.5.4 of the standard describes the constraints on implicit casting of pointers:

Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.

And the only constraint on explicitly casting pointers is:

A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type.

The relevant section to the first rule, 6.5.16.1, has the following rule for simple assignment:

the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

Finally, Section 6.7.3 on qualifiers has:

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

That sentence doesn't provide much value if lvalues with non-const-qualified types with access to objects defined with const-qualified types are themselves undefined. This indicates that you can explicitly cast a const void * to a void * and avoid the warning without introducing undefined behavior, as the warning relates specifically to an invalid use of implicit casting through simple assignment rather than a general objection to discarding the const qualifier.

Charles Gleason
  • 416
  • 5
  • 8
  • Nice answer, thanks for pointintg to that sections of the standard, _just adding an explicit cast would also avoid the warning_, not with `Wcast-qual` on, maybe my flags are too pedantic. – David Ranieri Oct 07 '19 at 07:29
  • Clearly mine are not pedantic enough; so much for -Wall -Wextra. It might be a bit intrusive in this case, though, since `Wdiscarded-qualifiers` seems to be more serious – Charles Gleason Oct 07 '19 at 20:50
1

The initialization void *b = a; isn't valid C, it violates the rule of simple assignment C17 6.5.16.1 (initialization follows the rules of assignment), which states that in order for the expression to be valid:

...the type pointed to by the left has all the qualifiers of the type pointed to by the right.

You might want to compile with -pedantic-errors to get errors instead of warnings for C language violations.


As for well-defined behavior - just as long as you de-reference the pointer using the correct type of the actual data, it is well-defined behavior, and the type of the pointer itself doesn't matter much.

I don't even understand why you need to convert to void*, since the format of your callback is this:

int (*comp)(const void *, const void *)

So the only problem is the return type of the outer function, which could be simplified to something like this:

void* vector_lsearch (const void* key, const void* base, int (*comp)(const void*, const void*))
{
    const struct vector* vector = CONST_VECTOR(base);
    void* result = NULL;
    unsigned char* data = (unsigned char*)base;

    for (size_t i=0; i < vector->size; i++)
    {
        if (comp(&data[i*vector->szof], key) == 0)
        {
            result = data;
            break;
        }
    }
    return result;
}

CONST_VECTOR is fishy though, smells like you are hiding a cast behind a macro or something?

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Yes, `CONST_VECTOR` is behind an ugly cast: https://github.com/davranfor/c/blob/master/vector/vector.c – David Ranieri Oct 07 '19 at 09:09
  • The thing is that I'm compiling with Wcast-qual and as you know `result = (void*)base;` raises a warning when this flag is on – David Ranieri Oct 07 '19 at 09:11
  • @DavidRanieri Why is that macro using `- offsetof` instead of `+ offsetof`? – Lundin Oct 07 '19 at 09:11
  • @DavidRanieri So don't compile with `-Wcast-qual`, or temporary disable it for that function with pragmas, https://stackoverflow.com/questions/3378560/how-to-disable-gcc-warnings-for-a-few-lines-of-code. – Lundin Oct 07 '19 at 09:15
  • Because in the implementation a pointer to `data` is passed to the functions, and the way to know where the `struct`(`vector`) embedding the data starts is looking at the offset of `data`, notice the `max_align_t` in the structure, related question: https://stackoverflow.com/questions/55873051/using-max-align-t-to-store-a-chunk-of-bytes – David Ranieri Oct 07 '19 at 09:16
  • Yes, Wcast-qual seems too restrictive, I'm gonna relax my warnings – David Ranieri Oct 07 '19 at 09:17
  • 2
    @DavidRanieri The root of all your problems is the bad API design of the C standard lib. The `bsearch`/`qsort` functions should never had returned `void*` but instead an integer index. But it is what it is, we have to cast away const because of the crappy API. – Lundin Oct 07 '19 at 09:24