1
votes

Copiez "pointeur vers const" vers "pointeur vers non const" via memcpy

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

12 commentaires

pourquoi pas simplement typé? Un avertissement vous indique que vous ne voudrez peut-être pas faire cela et qu'en faisant le typage, vous décrivez votre intention de le faire. memcpy () ne devrait faire aucune différence sauf que ce serait moins clair pour un autre lecteur pourquoi vous avez fait ça.


C'est aussi sûr que d'ignorer l'avertissement.


Ce ne sont pas des pointeurs const, ce sont des pointeurs vers const. Pourquoi avez-vous besoin de cette conversion?


Bien qu'il soit parfois prudent d'ignorer l'avertissement, dans presque tous les cas, le fait de faire taire un tel avertissement en convertissant des données constantes en données non constantes devrait être un signal d'alarme indiquant quelque chose de mauvais dans votre implémentation ou votre conception.


@Mat: pour faire une recherche linéaire sur un tableau const :, ie const int arr [] = {1, 2, 3}; , comme bsearch le fait.


Vous n'avez pas besoin d'une conversion const pour faire cela. Vous en auriez besoin si vous vouliez modifier les éléments du tableau (ce qui serait un comportement indéfini dans ce cas, toute la raison de l'avertissement). Avec const int * a; , a est modifiable (a ++ est valide), * a ne l'est pas.


@Mat, je sais, mais en passant le tableau comme dans cet exemple: ideone.com/UdwbJo renvoie un: < i> avertissement: passer l'argument 1 de 'func' supprime le qualificatif 'const' du type de cible du pointeur [-Wdiscarded-qualifiers]


Ignorer, transtyper ou memcpy ing tout est dangereux en ce sens que vous pouvez faire référence à des données immuables (par exemple des chaînes littérales) comme modifiables. Pas vraiment de problème tant que vous ne modifiez pas les données. Mais alors pourquoi ne pas travailler avec des pointeurs vers const en interne également et renvoyer un pointeur void const ? C'est l'option la plus sûre. C'est l ' utilisateur de votre fonction qui doit savoir s'il peut renvoyer le résultat en non-const, mais il sait aussi avec quoi la fonction a été appelée ...


@DavidRanieri Si la fonction à laquelle vous passez le pointeur n'a pas l'intention de modifier les données, faites en sorte que l'argument du pointeur soit constant dans la signature de la fonction. Si la fonction doit modifier les données ... eh bien, vous ne devriez pas passer de pointeur vers des données const.


@Jabberwocky car il s'agit probablement de C, pas de C ++.


@Aconcagua, bon point, mais cela fait partie d'une implémentation d'un vecteur , en interne, le contenu des vecteurs doit être modifiable.


@DavidRanieri Je ne suis pas d'accord: void * v = vector_create (...); void const * cv = v; someCallbackFunction (cv); - pour une raison quelconque, je ne veux pas que l'utilisateur fournissant le rappel modifie le vecteur. Mais la recherche linéaire «vole» ma const, l'utilisateur n'obtiendra même pas d'avertissement s'il essaie de modifier les données. Donc, si vous passez des données const, la sortie doit également rester const, si vous faites référence à ces données const.


3 Réponses :


2
votes

Cet avertissement vient de la suppression du qualificatif const dans le cadre de l'initialisation; ajouter simplement un cast explicite éviterait également l'avertissement.

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

La section 6.5.4 de la norme décrit les contraintes sur le cast implicite des pointeurs:

Les conversions qui impliquent des pointeurs, autres que celles autorisées par les contraintes de 6.5.16.1, doivent être spécifié au moyen d'un cast explicite.

Et la seule contrainte sur la conversion explicite des pointeurs est:

Un type pointeur ne doit pas être converti en un type flottant. Un type flottant ne doit pas être converti en tout type de pointeur.

La section pertinente de la première règle, 6.5.16.1, contient la règle suivante pour une affectation simple:

l'opérande de gauche a un type de pointeur atomique, qualifié ou non qualifié, et (compte tenu du type l'opérande de gauche aurait après la conversion de lvalue) les deux opérandes sont des pointeurs vers qualifiés ou des versions non qualifiées de types compatibles, et le type pointé par la gauche a tous les qualificatifs du type pointé par la droite;

Enfin, la section 6.7.3 sur les qualificatifs a:

Si une tentative est faite pour modifier un objet défini avec un type qualifié const en utilisant un lvalue avec un type non qualifié const, le comportement n'est pas défini.

Cette phrase ne fournit pas beaucoup de valeur si les lvalues ​​avec des types non qualifiés const avec accès aux objets définis avec des types qualifiés const sont elles-mêmes indéfinies. Cela indique que vous pouvez explicitement convertir un const void * en void * et éviter l'avertissement sans introduire de comportement indéfini, car l'avertissement concerne spécifiquement une utilisation non valide du cast implicite via une simple affectation plutôt qu'une objection générale à la suppression du qualificatif const.


2 commentaires

Bonne réponse, merci pour le pointintg sur ces sections de la norme, le simple fait d'ajouter une distribution explicite éviterait également l'avertissement , pas avec Wcast-qual activé, peut-être que mes indicateurs le sont aussi pédant.


Il est clair que les miens ne sont pas assez pédants; tant pis pour -Wall -Wextra. Cela peut être un peu intrusif dans ce cas, cependant, car Wdiscarded-qualifiers semble être plus sérieux



3
votes

Vous pouvez copier le pointeur en toute sécurité. Le problème de sécurité potentiel est lorsque vous utilisez b . Puisqu'il est déclaré comme un pointeur vers des données non constantes, vous pouvez l'assigner via le pointeur, par exemple * (int * b) = 1; Si quelque chose est une donnée constante, cela entraînera un comportement non défini.

Si vous utilisez le pointeur void * comme conduit qui finira par passer le pointeur vers une fonction qui reconvertira le pointeur dans son type d'origine (comme la façon dont qsort ( ) utilise son argument pointeur), vous devriez pouvoir ignorer cet avertissement. Vous vous attendez à ce que cette fonction la transforme en un pointeur vers const et n'essaye pas de l'assigner par son intermédiaire.

Je ne pense pas qu'il y ait un moyen de déclarer un pointeur générique qui puisse être utilisé comme conduit pour des données const ou non const. Si vous le déclarez non-const, vous recevrez un avertissement lorsque vous lui attribuerez un pointeur const; si vous le déclarez const, vous ne pourrez pas l'utiliser pour les fonctions qui veulent un pointeur non-const.


3 commentaires

void * b = a; n'est même pas valide C, ce n'est pas "juste un avertissement", c'est une violation de la langue.


@Lundin Est-ce une erreur du compilateur qu'il reçoit juste un avertissement?


La norme C ne mentionne que les "messages de diagnostic". Donc, le compilateur est conforme tant qu'il a informé le programmeur d'une manière ou d'une autre, que ce soit par un avertissement, une erreur ou une lettre manuscrite livrée avec un pigeon voyageur :) Mon point est que "Copier le pointeur en toute sécurité" n'est pas vraiment correct compte tenu du code de l'OP. Il faudrait plutôt dire "Il est impossible de copier le pointeur". Que le compilateur ait produit un binaire malgré l'avertissement, et ce que ce binaire est garanti de faire, est une autre histoire.



1
votes

L'initialisation void * b = a; n'est pas valide C, elle viole la règle d'affectation simple C17 6.5.16.1 (l'initialisation suit les règles d'affectation), qui stipule que dans l'ordre l'expression pour être valide:

... le type pointé par la gauche a tous les qualificatifs du type pointé par la droite.

Vous voudrez peut-être compiler avec -pedantic-errors pour obtenir des erreurs au lieu d'avertissements pour les violations du langage C.


Comme pour bien défini comportement - tant que vous dé-référencez le pointeur en utilisant le type correct des données réelles, il s'agit d'un comportement bien défini et le type du pointeur lui-même n'a pas beaucoup d'importance.

Je ne comprends même pas pourquoi vous devez convertir en void * , puisque le format de votre rappel est le suivant:

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

Le seul problème est donc le type de retour de la fonction externe, qui pourrait être simplifié en quelque chose comme ceci:

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

CONST_VECTOR est louche cependant, on dirait que vous cachez un casting derrière une macro ou quelque chose comme ça?


7 commentaires

Oui, CONST_VECTOR est derrière un casting laid: github.com/davranfor/c/blob/master/vector/vector.c


Le fait est que je compile avec Wcast-qual et comme vous le savez, result = (void *) base; déclenche un avertissement lorsque cet indicateur est activé


@DavidRanieri Pourquoi cette macro utilise-t-elle - offsetof au lieu de + offsetof ?


@DavidRanieri Donc, ne compilez pas avec -Wcast-qual , ou désactivez-le temporairement pour cette fonction avec pragmas, stackoverflow.com/questions/3378560/... .


Parce que dans l'implémentation, un pointeur vers data est passé aux fonctions, et le moyen de savoir où commence la struct ( vecteur ) incorporant les données regarde le décalage des données , notez le max_align_t dans la structure, question connexe: stackoverflow.com/questions/55873051/...


Oui, Wcast-qual semble trop restrictif, je vais relâcher mes avertissements


@DavidRanieri La racine de tous vos problèmes est la mauvaise conception d'API de la bibliothèque standard C. Les fonctions bsearch / qsort n'auraient jamais dû renvoyer void * mais à la place un index entier. Mais c'est ce que c'est, nous devons rejeter const à cause de l'API merdique.