简体   繁体   中英

Copy “pointer to const” to “pointer to non const” via memcpy

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;
}

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.

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, eg *(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.

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM