web-dev-qa-db-fra.com

Les compilateurs C ++ modernes sont-ils capables d'éviter d'appeler une fonction const deux fois dans certaines conditions?

Par exemple, si j'ai ce code:

class SomeDataProcessor
{
public:
    bool calc(const SomeData & d1, const SomeData & d2) const;
private:
    //Some non-mutable, non-static member variables
}

SomeDataProcessor sdp;
SomeData data1;
SomeData data2;

someObscureFunction(sdp.calc(data1, data2),
                    sdp.calc(data1, data2));

Prenons le code équivalent potentiellement:

bool b = sdp.calc(data1, data2);
someObscureFunction(b,b);

Pour que cela soit valide, la fonction calc() doit répondre à certaines exigences, et pour l'exemple j'appelle la propriété _pure_const_formula_

Un _pure_const_formula_:

  • Ne modifier aucun état de variable membre, statique ou globale
  • Appelez uniquement les fonctions _pure_const_formula_
  • Peut-être d'autres conditions que je n'ai pas en tête

Par exemple, appeler un générateur de nombres aléatoires ne répondrait pas à ces exigences.

Le compilateur est-il autorisé à remplacer le premier code par le second, même s'il doit creuser récursivement dans les fonctions appelées? Les compilateurs modernes sont-ils capables de le faire?

37
galinette

GCC a le pureattribut (utilisé comme __attribute__((pure))) pour les fonctions qui indique au compilateur que les appels redondants peuvent être éliminés. Il est utilisé par exemple sur strlen.

Je ne suis au courant d'aucun compilateur faisant cela automatiquement, surtout compte tenu du fait que les fonctions à appeler peuvent ne pas être disponibles sous forme source, et les formats de fichier objet ne contiennent aucune métadonnée sur le caractère pur ou non d'une fonction.

46
Tamás Zahola

Oui, absolument.

Les compilateurs font cela tout le temps, et plus .

Par exemple, si toute votre fonction a été renvoyée true et que sa définition était visible par le compilateur sur le site d'appel, l'appel de fonction entier serait probablement élidé, ce qui se traduirait par:

someObscureFunction(true, true);

Un programme pour lequel le compilateur a suffisamment d'informations peut être "optimisé" à partir d'une chaîne de tâches assez complexe jusqu'à peut-être une ou deux instructions. Maintenant, opérer sur les variables membres pousse l'optimiseur à sa limite dans une certaine mesure, mais si les variables sont private, reçoivent une valeur initiale connue et ne sont mutées par aucune autre fonction membre, je ne le fais pas '' t voir pourquoi un compilateur ne pourrait pas simplement incorporer sa valeur connue s'il le voulait. Les compilateurs sont très, très intelligents.

Les gens pensent qu'un programme compilé est un mappage un à un des lignes de votre code source, mais ce n'est presque jamais vrai. Le but entier de C++ est qu'il s'agit d'une abstraction de ce que votre ordinateur va réellement faire quand il exécutera votre programme.

22

Non, étant donné le code affiché, le compilateur ne peut garantir que l'optimisation proposée n'aura aucune différence observable, et aucun compilateur moderne ne pourra optimiser loin le deuxième appel de fonction.

Un exemple très simple: cette méthode de classe peut utiliser un générateur de nombres aléatoires et enregistrer le résultat dans un tampon privé, qu'une autre partie du code lira plus tard. De toute évidence, l'élimination d'un appel de fonction entraîne désormais moins de valeurs générées de façon aléatoire placées dans ce tampon.

En d'autres termes, ce n'est pas parce qu'une méthode de classe est const qu'elle n'a pas d'effets secondaires observables lorsqu'elle est appelée.

10
Sam Varshavchik

Non, le compilateur n'est pas autorisé à le faire dans ce cas. Le const signifie uniquement que vous ne modifiez pas l'état de l'objet auquel la méthode appartient. Cependant, invoquer cette méthode plusieurs fois avec les mêmes paramètres d'entrée peut donner des résultats différents. Par exemple, pensez à une méthode qui produit un résultat aléatoire.

4
Axel

Oui, les compilateurs C modernes peuvent éliminer les appels de fonction redondants si et seulement si ils peuvent prouver qu'une telle optimisation se comporte comme -si la sémantique du programme d'origine a été suivie. Par exemple, cela signifie qu'ils pourraient éliminer plusieurs appels à la même fonction avec les mêmes arguments, si la fonction n'a pas d'effets secondaires et si sa valeur de retour dépend uniquement des arguments.

Maintenant, vous avez posé une question spécifique sur const - c'est surtout utile au développeur, et non au codeur. Une fonction const est un indice que la méthode ne modifie pas l'objet sur lequel elle est appelée, et const les arguments sont des indices que les arguments ne sont pas modifiés. Cependant, la fonction peut (légalement1) supprime le const- ness du pointeur this ou de ses arguments. Le compilateur ne peut donc pas s'y fier.

De plus, même si les objets const passés à une fonction n'ont jamais vraiment été modifiés dans cette fonction et que les fonctions const n'ont jamais modifié l'objet récepteur, la méthode pourrait facilement s'appuyer sur des données globales mutables (et pourrait muter ces données). Considérons, par exemple, une fonction qui renvoie l'heure actuelle ou qui incrémente un compteur global.

Ainsi, les déclarations const aident le programmeur, pas le compilateur2.

Cependant, le compilateur peut être en mesure d'utiliser d'autres astuces pour prouver que les appels sont redondants:

  • La fonction peut se trouver dans la même unité de compilation que l'appelant, ce qui permet au compilateur de l'inspecter et de déterminer exactement sur quoi elle s'appuie. La forme ultime de ceci est inline: le corps de la fonction peut être déplacé dans l'appelant à quel point l'optimiseur peut supprimer le code redondant des appels ultérieurs (jusqu'à un incluant tout le code de ces appels entièrement et peut-être tout ou le port de l'appel d'origine aussi).
  • La chaîne d'outils peut utiliser un certain type d'optimisation de temps de liaison, ce qui permet effectivement le type d'analyse décrit dans le point ci-dessus même pour les fonctions et les appelants dans différentes unités de compilation. Cela pourrait permettre cette optimisation pour tout code présent lors de la génération de l'exécutable final.
  • Le compilateur peut permettre à l'utilisateur d'annoter une fonction avec un attribut qui informe le compilateur qu'il peut traiter la fonction comme n'ayant pas d'effets secondaires. Par exemple, gcc fournit les attributs de fonction pure et const qui informent gcc que les fonctions n'ont pas d'effets secondaires et dépendent uniquement de leurs paramètres (et sur les variables globales, dans le cas de pure).

Habituellement, tant que l'objet n'a pas été défini à l'origine comme const.

Il y a un sens dans lequel const définitions aident le compilateur: elles peuvent mettre des objets globaux définis comme const dans une section en lecture seule de l'exécutable (si une telle fonctionnalité existe) et également combiner ces objets lorsqu'ils sont égaux (par exemple, des constantes de chaîne identiques).

1
BeeOnRope