Y at-il un avantage pratique pour lancer un pointeur NULL à un objet et d'appeler l'un de ses fonctions membres?

StackOverflow https://stackoverflow.com/questions/2569602

Question

Ok, donc je sais que cela est techniquement un comportement non défini, mais néanmoins, je l'ai vu plus d'une fois dans le code de production. Et s'il vous plaît me corriger si je me trompe, mais je l'ai aussi entendu dire que certaines personnes utilisent cette « fonctionnalité » comme un substitut peu légitime pour un aspect manquant de la norme actuelle C ++, à savoir l'impossibilité d'obtenir l'adresse (bien, décalage vraiment) d'une fonction de membre. Par exemple, cela est hors d'une mise en œuvre populaire d'une bibliothèque PCRE (Perl-compatible Expression régulière):

#ifndef offsetof
#define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field))
#endif

On peut se demander si l'exploitation d'une telle subtilité linguistique dans un cas comme celui-ci est valide ou non, ou même nécessaire, mais je l'ai aussi vu utilisé comme ceci:

struct Result
{
   void stat()
   {
      if(this)
         // do something...
      else
         // do something else...
   }
};

// ...somewhere else in the code...

((Result*)0)->stat();

Cela fonctionne très bien! Il évite un déréférencement de pointeur nul en testant l'existence de this, et il ne cherche pas à accéder aux membres de la classe dans le bloc else. Tant que ces gardes sont en place, il est légitime de code, non? La question reste: Y at-il un cas d'utilisation pratique, où l'on aurait intérêt à utiliser une telle construction? Je suis particulièrement préoccupé par le second cas, le premier cas est plus d'une solution de contournement pour une limitation de la langue. Ou est-il?

PS. Désolé pour le style C jette, malheureusement les gens préfèrent encore taper moins si possible.

Était-ce utile?

La solution

Le premier cas ne demande pas quoi que ce soit. Il prend l'adresse . C'est défini, permis, fonctionnement. Elle donne le décalage en octets depuis le début de l'objet dans le champ spécifié. Ceci est un très, très, pratique courante, car les décalages comme celui-ci sont très souvent nécessaires. Tous les objets peuvent être créés sur la pile, après tout.

Le second cas est assez stupide. La seule chose sensée serait de déclarer que statique de la méthode.

Autres conseils

Je ne vois aucun avantage de ((Result*)0)->stat(); - c'est un bidouille horrible qui cassera probablement plus tôt que tard. La bonne approche C serait de l'aide d'un Result::stat() de méthode statique.

offsetof () d'autre part est légal, comme la macro appelle jamais réellement offsetof () une méthode ou accède à un membre, mais effectue uniquement des calculs d'adresse.

Tout le monde a fait un bon travail de réitérant que le comportement est indéfini. Mais laisse prétendre que ce n'était pas, et que p->member est autorisé à se comporter d'une manière cohérente dans certaines circonstances, même si p est pas un pointeur valide.

Votre deuxième construction servirait encore presque rien. Du point de vue de la conception, vous avez probablement fait quelque chose de mal si une seule fonction peut faire son travail à la fois avec et sans membres un accès, et si peut puis diviser la partie statique du code dans une procédure distincte, fonction statique serait beaucoup plus raisonnable que d'attendre vos utilisateurs de créer un pointeur nULL pour fonctionner sur.

Du point de vue de la sécurité, vous avez seulement protégé contre une petite partie des moyens d'un pointeur invalide this peut être créé. Il y a des pointeurs non initialisées, pour commencer:

Result* p;
p->stat(); //Oops, 'this' is some random value

Il y a des pointeurs qui ont été initialisés, mais sont encore valides:

Result* p = new Result;
delete p;
p->stat(); //'this' points to "safe" memory, but the data doesn't belong to you

Et même si vous initialisez toujours vos pointeurs, et absolument jamais réutiliser accidentellement la mémoire free'd:

struct Struct {
    int i;
    Result r;
}
int main() {
    ((Struct*)0)->r.stat(); //'this' is likely sizeof(int), not 0
}

Alors, vraiment, même si elle était un comportement non défini, ce comportement est sans valeur.

Bien que les bibliothèques ciblant les implémentations C ++ spécifiques peuvent faire cela, cela ne signifie pas qu'il est généralement « légitime ».

  

Cela fonctionne très bien! Il évite une nulle   pointeur déréférencement par test pour la   existence de cela, et il ne cherche pas   pour accéder aux membres de la classe dans l'autre   bloquer. Tant que ces gardes sont   lieu, il est légitime de code, droit?

Non, parce que bien qu'il puisse fonctionner correctement sur certaines implémentations C ++, il est tout à fait correct pour que cela fonctionne sur aucune mise en œuvre conforme C ++.

Déréférencer un pointeur NULL est un comportement non défini et tout peut arriver si vous le faites. Ne pas le faire si vous voulez un programme qui fonctionne.

Juste parce qu'il ne se bloque pas immédiatement dans un cas de test spécifique ne signifie pas qu'il ne sera pas vous mettre dans toutes sortes de problèmes.

comportement non défini est un comportement non défini. Pour ce faire, trucs « travail » pour votre compilateur particulier? bien, peut-être. vont-ils travailler pour la prochaine itération de celui-ci. ou pour un autre compilateur? Peut-être pas. Vous Verse votre argent et vous prend votre choix. Je peux seulement dire que, dans près de 25 années de programmation C ++, je ne l'ai jamais ressenti le besoin de faire aucune de ces choses.

En ce qui concerne la déclaration:

  

Il évite un déréférencement de pointeur nul en testant l'existence de ce fait, et il ne cherche pas à accéder aux membres de la classe dans le bloc else. Tant que ces gardes sont en place, il est légitime de code, droit?

Le code n'est pas légitime. Il n'y a aucune garantie que le compilateur et / ou l'exécution se fait appel à la méthode lorsque le pointeur est NULL. La vérification de la méthode est d'aucune aide parce que vous ne pouvez pas supposer que la méthode réellement finir par être appelé avec un pointeur NULL de this.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top