37 votes

Quand utiliser const void* ?

J'ai cette fonction de test très simple que j'utilise pour comprendre ce qui se passe avec le qualificatif const.

int test(const int* dummy)
{
   *dummy = 1;
   return 0;
}

Celui-ci me lance une erreur avec GCC 4.8.3. Pourtant, celui-ci compile :

int test(const int* dummy)
{
   *(char*)dummy = 1;
   return 0;
}

Il semble donc que le qualificatif const ne fonctionne que si j'utilise l'argument sans le transformer en un autre type.

Récemment, j'ai vu des codes qui utilisaient

test(const void* vpointer, ...)

En tout cas pour moi, quand j'ai utilisé void*, j'ai eu tendance à le couler en char* pour l'arithmétique des pointeurs dans les piles ou pour le traçage. Comment const void* empêcher les fonctions de sous-routine de modifier les données auxquelles vpointer est en train de pointer ?

7 votes

const T* signifie que les données a souligné par le pointeur est const. La seconde est très fragile, puisqu'elle rejette le const.

1 votes

@ColonelThirtyTwo Mais d'après ce que j'ai lu sur d'autres posts stackoverflow, est-ce que const void* n'est pas souvent utilisé pour empêcher la modification des données ? Mais il me semble qu'en faisant un casting, elle peut être modifiée très facilement.

1 votes

@MoneyBall oui, mais le casting est comme ça. C'est pour cela que le casting est dangereux - je peux prendre votre "const int * " et le convertir en "int64_t * " et SIGSVF ou corrompre d'autres données. Le problème se situe au niveau de la moulage pas les déclarations de const.

54voto

bolov Points 4005
const int *var;

const est un contrat . En recevant un const int * vous "dites" à l'appelant que vous (la fonction appelée) ne modifierez pas les objets vers lesquels le pointeur pointe.

Votre deuxième exemple explicite rompt ce contrat en supprimant le qualificatif const et en modifiant ensuite l'objet pointé par le pointeur reçu. Ne faites jamais cela.

Ce "contrat" est appliqué par le compilateur. *dummy = 1 ne compilera pas. Le cast est un moyen de contourner cela, en disant au compilateur que vous savez vraiment ce que vous faites et de vous laisser faire. Malheureusement, le "je sais vraiment ce que je fais" n'est généralement pas le cas.

const peut également être utilisé par le compilateur pour effectuer une optimisation qu'il ne pourrait pas faire autrement.


Note sur le comportement indéfini :

Veuillez noter que si le cast lui-même est techniquement légal, la modification d'une valeur déclarée en tant que const est un comportement indéfini. Donc techniquement, la fonction originale est ok, tant que le pointeur qui lui est passé pointe vers des données déclarées mutables. Sinon, c'est un comportement indéfini.

Plus d'informations à ce sujet à la fin de l'article


Pour ce qui est de la motivation et de l'utilisation, prenons les arguments suivants strcpy et memcpy fonctions :

char* strcpy( char* dest, const char* src );
void* memcpy( void* dest, const void* src, std::size_t count );

strcpy opère sur des chaînes de caractères, memcpy opère sur des données génériques. Bien que j'utilise strcpy comme exemple, la discussion suivante est exactement la même pour les deux, mais avec char * et const char * pour strcpy et void * et const void * pour memcpy :

dest est char * car dans le tampon dest la fonction mettra la copie. La fonction va modifier le contenu de ce tampon, donc il n'est pas const.

src est const char * parce que la fonction ne lit que le contenu du tampon src . Il ne le modifie pas.

C'est seulement en regardant la déclaration de la fonction qu'un appelant peut affirmer tout ce qui précède. Par contrat strcpy ne modifiera pas le contenu du second tampon passé en argument.


const et void sont orthogonales. C'est toute la discussion ci-dessus sur const s'applique à tout type ( int , char , void , ...)

void * est utilisé en C pour les données "génériques".


Encore plus sur le comportement indéfini :

Cas 1 :

int a = 24;
const int *cp_a = &a; // mutabale to const is perfectly legal. This is in effect
                      // a constant view (reference) into a mutable object

*(int *)cp_a = 10;    // Legal, because the object referenced (a)
                      // is declared as mutable

Cas 2 :

const int cb = 42;
const int *cp_cb = &cb;
*(int *)cp_cb = 10;    // Undefined Behavior.
                       // the write into a const object (cb here) is illegal.

J'ai commencé par ces exemples car ils sont plus faciles à comprendre. De là, il n'y a qu'un pas vers les arguments de fonction :

void foo(const int *cp) {
    *(int *)cp = 10;      // Legal in case 1. Undefined Behavior in case 2
}

Cas 1 :

int a = 0;
foo(&a);     // the write inside foo is legal

Cas 2 :

int const b = 0;
foo(&b);     // the write inside foo causes Undefined Behavior

J'insiste à nouveau : à moins que vous ne sachiez vraiment ce que vous faites, que toutes les personnes travaillant actuellement et à l'avenir sur le code soient des experts et le comprennent, et que vous ayez une bonne motivation, à moins que toutes les conditions ci-dessus soient réunies, ne jamais rejeter la constance !!

0 votes

Tout d'abord, merci pour tous ces exemples. Il est clair pour moi que même si const établit une sorte de contrat entre l'appelant et l'appelé, l'appelé peut facilement rejeter le qualificatif const. Peut-on dire que le qualificatif const ne garantit PAS que la valeur pointée par le pointeur const void* est constante ?

5 votes

@MoneyBall On peut dire que C vous donne des méthodes pour vous tirer dans les pattes (permettre de rejeter le qualificatif const est l'une d'entre elles). Ceci étant dit, on peut supposer qu'une fonction ne rejettera pas le qualificatif const. À moins qu'il ne s'agisse d'une fonction malveillante ou d'un bogue.

2 votes

@MoneyBall J'ai ajouté une note sur l'application de ce contrat par le compilateur.

8voto

black Points 2917
int test(const int* dummy)
{
   *(char*)dummy = 1;
   return 0;
}

Non, cela ne fonctionne pas. Chasser la constance (avec vraiment const données) est comportement indéfini et votre programme se plantera probablement si, par exemple, l'implémentation a mis const données dans la ROM. Le fait que "ça marche" ne change rien au fait que votre code est mal formé.

En tout cas pour moi, quand j'utilise void*, j'ai tendance à le convertir en char* pour l'arithmétique des pointeurs dans les piles ou pour le traçage. l'arithmétique des pointeurs dans les piles ou pour le traçage. Comment const void* peut-il empêcher les fonctions de sous-routine de modifier les données sur lesquelles le vpointer pointe ?

A const void* signifie un pointeur vers des données qui ne peuvent pas être modifiées. Pour le lire, oui, vous devez le convertir en types concrets tels que char . Mais j'ai dit lecture pas écrire qui, encore une fois, est UB.

Ce sujet est traité plus en profondeur ici . C vous permet de contourner entièrement la sécurité des types : c'est à vous de l'empêcher.

3 votes

En fait, rejeter la constance n'est pas un comportement indéfini. Il est même bien défini pour modifier la valeur pointée par, à la condition que l'objet ait été déclaré mutable. Voir ma réponse pour un exemple détaillé. Mais je suis 100% d'accord que ce n'est pas une raison pour autoriser le casting away constness.

0 votes

@bolov Oui, merci de le signaler. J'ai en quelque sorte fait allusion à cela quand j'ai mentionné une implémentation qui place const données dans la ROM.

4voto

Lorehead Points 953

Il est possible qu'un compilateur donné, sur un système d'exploitation donné, mette certains de ses éléments d'information à la disposition des utilisateurs. const des données dans des pages de mémoire en lecture seule. Si c'est le cas, une tentative d'écriture à cet emplacement échouerait sur le plan matériel, en provoquant par exemple une erreur de protection générale.

Le site const signifie simplement que l'écriture est comportement indéfini . Cela signifie que le langage standard permet au programme de se planter si vous le faites (ou quoi que ce soit d'autre). Malgré cela, le C vous permet de vous tirer une balle dans le pied si vous pensez savoir ce que vous faites.

Vous ne pouvez pas empêcher une sous-routine de réinterpréter les bits que vous lui donnez comme elle le souhaite et d'exécuter n'importe quelle instruction machine sur ceux-ci. La fonction de bibliothèque que vous appelez pourrait même être écrite en assembleur. Mais faire cela à un const Le pointeur est comportement indéfini et vous ne voulez vraiment pas invoquer comportement indéfini .

Je vous propose un exemple rare où cela pourrait avoir un sens : supposez que vous ayez une bibliothèque qui transmet des paramètres de manipulation. Comment les génère-t-elle et les utilise-t-elle ? En interne, ils peuvent être des pointeurs vers des structures de données. C'est donc une application où vous pourriez typedef const void* my_handle; Le compilateur émettra donc une erreur si vos clients essaient de le déréférencer ou de faire de l'arithmétique dessus par erreur, puis de le retransférer en un pointeur vers votre structure de données dans les fonctions de votre bibliothèque. Ce n'est pas l'implémentation la plus sûre, et vous devez faire attention aux attaquants qui peuvent passer des valeurs arbitraires à votre bibliothèque, mais c'est très peu coûteux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X