29
votes

Les adresses de deux temporaires sont-elles garanties d'être différentes dans la même expression?

Considérez le programme suivant:

#include <iostream>

int const * f(int const &i) 
{ 
  return &i; 
}

int main() 
{
  std::cout << f(42);  // #1
  std::cout << f(42);  // #2

  std::cout << f(42) << f(42);  // #3
}

Selon le compilateur et le niveau d'optimisation défini, les adresses imprimées sur les lignes # 1 et # 2 peut ou non être différent les uns des autres.

Cependant, quel que soit le choix du compilateur ou des niveaux d'optimisation, les 2 adresses imprimées en ligne # 3 sont toujours différent les uns des autres.

Voici un démo pour jouer avec. p>

Alors, quelles sont les règles de ce que f renvoie dans chacun de ces cas?


10 commentaires

Est-ce que cela répond à votre question? C ++ Lifetime de temporaires - est-ce sûr?


@Languagelawyer pas vraiment. Bien que les passages pertinents semblent se chevaucher pour les deux questions, je pense que les questions elles-mêmes sont différentes.


Bien sûr, cela s'applique également si nous faisons de 42 une valeur macro de prétraitement. Personnellement, je ne vois pas pourquoi une norme nécessiterait duplication d'immutables identiques.


Il semble que tout le monde a raté ce <<< / code> ici a un comportement défini par l'implémentation aux pointeurs, donc si vous définissez "différent" lors du comportement du programme, il n'est tout simplement pas spécifié pour les implémentations conformes arbitraires.


Vous pouvez peut-être reformuler le programme en utilisant == à la place. Notez qu'il peut y avoir d'autres problèmes Dans certains cas légèrement différents ...


@FrankHB J'avais en effet envisagé d'utiliser == et d'autres moyens d'obtenir l'adresse dans la même expression, mais ils avaient des problèmes que vous le soulignez. Le <<< / code> pour les pointeurs en cours de mise en œuvre définie est intéressant. Je ne pense pas que cela affecte la question en particulier, car la réponse devrait demander tout choix de mise en œuvre.


Il y a aussi une autre question similaire ici: Force C ++ pour attribuer de nouvelles adresses à Arguments


@ Orçunçolak Oui, en fait c'est là que cette question s'est posée, comme on peut le voir dans les commentaires sur la réponse acceptée.


Deux objets différents sont juste ... pas les mêmes, car ils ont des identités différentes . La notion d'identité est certainement plus largement utilisée, par exemple pour les LVAlues, même s'il est également soigneusement esquivé dans la plupart des contextes. Si la différence d'identité est intéressée, la spécification oblige simplement les seules manières autorisées des accès aux objets (par exemple, des règles d'aliasing strictes), car le nombre d'objets ici est considéré comme un détail de mise en œuvre. Une adresse est conçue dérivée de l'identité des objets et il ne peut pas vous aider à rendre la différence plus évidente.


Il convient de s'appuyer sur la notion d'adresse pour décrire la disposition entre différents objets (et sous-objets de ceux-ci). Dans ce contexte particulier, l'identité ne suffit pas. Ce n'est pas le cas ici (chevauchement de la durée de vie, ne se chevauchant pas le stockage). Le raisonnement devient le chaos lorsque des adresses sont impliquées. Comme répondu, comme les règles AS-IF sont efficaces, les différents objets peuvent avoir la même adresse lorsqu'il n'y a pas de moyen portable de différencier les adresses. Notez également que Addressof et [[No_Unique_Address]] ne nécessitent pas vraiment de différencier les adresses (mais juste des identités).


3 Réponses :


33
votes

Deux objets vivants en C ++ (presque) ont toujours des adresses différentes.

Étant donné que les temporaires dans # 1 # 2 ont des durées de vie non chevauchantes, le compilateur est libre de réutiliser le stockage du n ° 1 pour # 2.

Mais dans # 3, tous les temporaires sont vivants jusqu'à la fin de l'expression (pour des raisons évidentes) et dans ce cas, ils doivent avoir des adresses différentes.

C ++ ne prend pas en charge la mise en cache garantie des mêmes sous-expressions en dehors de la règle "comme si". Cela signifie que si vous ne prenez pas l'adresse, il est parfaitement raisonnable pour le compilateur de les stocker, mais il les aime ou ne les stockent pas du tout.

référence

n4861 draft c ++ 20 [6.7.9.2] À moins qu'un objet ne soit un peu de champ ou un sous-objet de taille zéro, le L'adresse de cet objet est l'adresse du premier octet qu'il occupe. Deux objets avec des durées de vie qui ne se chevauchent pas avoir la même adresse si l'un est imbriqué dans l'autre, ou si au moins un est un sous-objet de taille zéro et ils sont différents types; sinon, ils ont des adresses distinctes et occupent le disjoint octets de stockage. ^ 28

Dans votre cas, les exceptions ne s'appliquent pas. La note de bas de page ^ 28 dit également exactement ce que j'ai écrit ci-dessus:

^ 28 : sous le «As- Si »règle une implémentation est autorisée à stocker deux objets à la même adresse machine ou ne pas stocker du tout un objet si Le programme ne peut pas observer la différence.

modifier

Excellente question de @riad:

Mais ces deux 42 doivent-ils être des objets différents? Par exemple, "ABC" et "ABC" peuvent être le même tableau.

Le comportement dépend du type de littéral utilisé et est défini avec précision dans n4861 Draft C ++ 20 5.13 [lex.literal] .

  • Les littéraux de chaîne sont une exception parmi toutes les types littéraux car ils sont classés comme Lvalues ​​et ont donc une adresse.

    [lex.string.14] Évaluation d'une chaîne -Les résultats littéraux dans un objet littéral de chaîne avec durée de stockage statique, initialisé à partir des caractères donnés comme spécifié ci-dessus. Que tous les littéraux de chaîne soient distincts (c'est-à-dire sont stockés dans des objets non chevauchants) et si les évaluations successives d'un rendement littéral de chaîne sont non spécifiées.

    .

    signifie que les littéraux pourraient avoir la même adresse que @riad observé, mais cela n'est pas en contradiction avec ce qui précède car ils sont le même objet.

  • Tous les autres littéraux, y compris les entiers, sont des expressions de présalon qui ne sont pas des objets (dans un sens qu'ils n'ont pas d'adresse), mais dans certains cas, ils engendrent un objet temporaire par le biais de la matérialisation temporaire qui se produit pour foo (42) car il est lié à un const t & . Afaik La norme ne dit pas explicitement que les deux mêmes expressions de la pré-préchalé doivent engendrer un temporaire différent, mais il dit qu'une expression initialise un temporaire, donc je crois que chaque expression doit créer un nouveau temporaire, les durées de vie sont également légèrement différentes. Ainsi, deux adresses (si observées) doivent être différentes.


  • 4 commentaires

    Il existe cependant des exceptions (qui ne s'appliquent pas à l'affaire en question). À savoir les classes de base vides et les membres avec attribut [[no_uninique_address]] peuvent partager l'adresse avec d'autres sous-objets. Et bien sûr, le premier sous-objet partage l'adresse de son super.


    Mais ces deux 42 doivent-ils être des objets différents? Par exemple, "ABC" et "ABC" peuvent être le même tableau. gcc.godbolt.org/z/f4zptp


    ou encore plus proche de la forme de l'op gcc.godbolt.org/z/ry793j (note (note Que sa référence à un tableau réel transmis à la fonction, pas encore de décroissance à ce stade, donc ce ne sera pas l'explication)


    @Riad a édité la réponse, excellente question!



    18
    votes

    Les temporaires persistent jusqu'à la fin de l'expression complète qui les a fait prendre vie.

    [class.temporary]

    4 ... objets temporaires sont détruits comme la dernière étape Évaluer l'expression complète ([intro.execution]) qui (lexicalement) contient le point où ils ont été créés.

    Cela est vrai pour tous les temporaires. Cela signifie que dans l'expression n ° 3, en supposant que son évaluation se termine sans lancer une exception, les deux temporaires auraient pu se chevaucher des durées de vie.

    À quelques exceptions près (dont aucune s'applique ici), deux objets différents de leur vie auront des adresses différentes.


    0 commentaires

    1
    votes

    Certains de mes commentaires précédents ont republié ici comme demandé:

    La chose vraiment intéressante est que C ++ n'en oblige aucun encodage concret à l'adresse d'un objet. (Et il ne mentionne rien sur l'adresse d'une fonction, btw.) C'est naturel, car la machine abstraite C ++ n'a tout simplement aucun intérêt sur l'adresse dans la plupart des contextes.

    Deux objets différents sont juste ... pas les mêmes, car ils ont des identités différentes . La notion d'identité est certainement plus largement utilisée, par exemple pour les LVAlues, même s'il est également soigneusement esquivé dans la plupart des contextes. Si la différence d'identité est intéressée, la spécification oblige simplement les seules manières autorisées des accès aux objets (par exemple, des règles d'aliasing strictes), car le nombre d'objets ici est considéré comme un détail de mise en œuvre. Une adresse est dérivée de conception de l'identité des objets et il ne peut pas vous aider à rendre la différence plus évidente.

    Il convient de s'appuyer sur la notion d'adresse pour décrire la disposition entre différents objets (et sous-objets de ceux-ci). Dans ce contexte particulier, l'identité ne suffit pas. Ce n'est pas le cas ici (chevauchement de la durée de vie, ne se chevauchant pas le stockage). Le raisonnement devient le chaos lorsque des adresses sont impliquées. Comme répondu, comme les règles AS-IF sont efficaces, les différents objets peuvent avoir la même adresse lorsqu'il n'y a pas de moyen portable de différencier les adresses. Notez également que adressof et [[no_uninique_address]] ne nécessitent pas vraiment de différencier les adresses (mais juste des identités).


    0 commentaires