30 votes

Dans l'opérateur virgule, l'opérande gauche est-il garanti qu'il ne sera pas exécuté s'il n'a pas d'effets secondaires?

Pour montrer le sujet que je vais utiliser le mot C, mais la même macro peut également être utilisé en C++ (avec ou sans struct), ce qui soulève la même question.

Je suis venu avec cette macro

#define STR_MEMBER(S,X) (((struct S*)NULL)->X, #X)

Son but est d'avoir des chaînes de caractères (const char*) d'un membre d'un struct, de sorte que, si le membre n'existe pas, la compilation échoue. Un minimum d'exemple d'utilisation:

#include <stdio.h>

struct a
{
    int value;
};

int main(void)
{
    printf("a.%s member really exists\n", STR_MEMBER(a, value));
    return 0;
}

Si value n'étaient pas membre de l' struct a, le code ne compile pas, et c'est ce que je voulais.

L'opérateur virgule doit évaluer l'opérande de gauche, puis jeter le résultat de l'expression (si il y en a un), de sorte que ma compréhension est que souvent cet opérateur est utilisé lors de l'évaluation de l'opérande de gauche a des effets secondaires.

Dans ce cas, cependant, il n'y a pas (prévue) des effets secondaires, mais bien sûr qu'il fonctionne le forum le compilateur n'a pas fait de produire du code qui évalue l'expression, car, sinon, il aurait accès à un struct situé à l' NULL et une erreur de segmentation se produirait.

Gcc/g++ 6.3 et 4.9.2 n'a jamais produit que de code dangereux, même avec -O0,, comme si elles l'ont toujours été en mesure de "voir" que l'évaluation n'a pas d'effets secondaires et peut donc être ignoré.

L'ajout d' volatile de la macro (par exemple, parce que l'accès, l'adresse mémoire est le côté souhaité effet) était jusqu'à présent le seul moyen de déclencher l'erreur de segmentation.

Donc, la question: est-il rien dans les langages C et C++ standard qui garantit que les compilateurs évitera toujours de réelle évaluation de l'opérande gauche de l'opérateur virgule lorsque le compilateur peut être sûr que l'évaluation n'a pas d'effets secondaires?

Notes et fixation

Je ne demande pas un jugement sur la macro que c'est et à l'occasion de l'utiliser ou de le rendre meilleur. Pour les fins de cette question, la macro est mauvais si et seulement si elle évoque un comportement indéterminé - c'est à dire, si et seulement si il est risqué parce que les compilateurs sont autorisées à produire de l ' "évaluation" du code, même lorsque cela n'a pas d'effets secondaires.

J'ai déjà deux évident correctifs à l'esprit: "chosification" de l' struct et à l'aide de offsetof. L'ancien a besoin d'une mémoire accessible zone aussi grande que le plus grand struct - nous utiliser comme premier argument de STR_MEMBER (par exemple, peut-être un statique de l'union pourrait faire...). Ce dernier devrait fonctionner parfaitement: il donne un décalage qui ne nous intéresse pas, et évite le problème de l'accès - en effet, je suis en supposant que gcc, parce que c'est le compilateur que j'utilise (d'où l'étiquette), et que son offsetof intégré se comporte.

Avec l' offsetof corrigé de la macro devient

#define STR_MEMBER(S,X) (offsetof(struct S,X), #X)

La rédaction volatile struct S au lieu de struct S n'entraîne pas l'erreur de segmentation.

Des Suggestions sur d'autres possibles "correctifs" sont aussi les bienvenus.

Ajout de la note

En réalité, la véritable utilisation de cas était en C++ dans une mémoire statique struct. Cela semble aller pour le mieux dans le C++, mais dès que j'ai essayé C avec un code plus proche de l'original au lieu d'une bouillie pour cette question, j'ai réalisé que C n'est pas du tout satisfait de cette:

error: initializer element is not constant

C veut la struct être initializable au moment de la compilation, au lieu de C++ c'est bien aussi.

17voto

Matt McNabb Points 14273

Il n'y a rien dans les langages C et C++ standard qui garantit que les compilateurs évitera toujours de réelle évaluation de l'opérande gauche de l'opérateur virgule ?

C'est le contraire. La norme garantit que l'opérande de gauche EST évaluée (vraiment il ne, il ne sont pas tous des exceptions). Le résultat est ignoré.


Remarque: pour lvalue expressions, "évaluer" ne signifie pas "accéder à la valeur stockée". Au lieu de cela, cela signifie de travailler où désignés localisation de la mémoire. Le code des autres englobant la lvalue expression peut ou ne peut pas aller puis sur pour accéder à l'emplacement de la mémoire. Le processus de lecture à partir de l'emplacement de mémoire est connue comme la "lvalue de conversion" dans C, ou de la "lvalue à rvalue conversion" en C++.

En C++, un jeté de la valeur de l'expression (comme l'opérande gauche de l'opérateur virgule) a seulement une lvalue à rvalue conversion effectuée sur elle si elle est en volatile et répond également à d'autres critères (voir le C++14 [expr]/11 pour les détails). En C lvalue conversion ne se produire pour des expressions dont le résultat n'est pas utilisée (C11 6.3.2.1/2).

Dans votre exemple, il est discutable de savoir si ou de ne pas lvalue la conversion se produit. Dans les deux langues X->YX est un pointeur, est défini comme (*X).Y; en C de la loi de l'application de l' * d'un pointeur null déjà provoque un comportement indéterminé (C11 6.5.3/3), et en C++ l' . l'opérateur est défini uniquement pour le cas lorsque l'opérande de gauche en fait désigne un objet (C++14 [expr.ref]/4.2).

12voto

Quincunx Points 1923

L' opérateur virgule (C documentation, dit quelque chose de très similaire) n'a pas de telles garanties.

Dans un virgule expression E1, E2, l'expression E1 est évaluée, son résultat est mis au rebut ... et ses effets secondaires sont terminées avant l'évaluation de l'expression E2 commence

les informations non pertinentes omis

Pour le dire simplement, E1 seront évalués, bien que le compilateur peut optimiser loin par la si la règle s'il est en mesure de déterminer qu'il n'existe pas d'effets secondaires.

3voto

John Bollinger Points 16563

Vous demandez,

il n'y a rien dans les langages C et C++ standard qui garantit que les compilateurs évitera toujours de réelle évaluation de l'opérande de gauche de l'opérateur virgule lorsque le compilateur peut être sûr que la l'évaluation n'a pas d'effets secondaires?

Comme d'autres l'ont fait remarquer, la réponse est "non". Au contraire, les normes à la fois de façon inconditionnelle de l'état que la gauche opérande de l'opérateur virgule est évalué, et que le résultat est ignoré.

C'est bien sûr une description du modèle d'exécution d'une machine abstraite; des implémentations sont autorisés à travailler différemment, à condition que le comportement observable est le même que le résumé le comportement de la machine serait de produire. Si, en effet, l'évaluation de la gauche expression produit pas d'effets secondaires, alors que aurait permis de sauter tout à fait, mais il n'y a rien de standard qui fournit, pour exiger qu'elle soit ignorée.

Comme pour le fixer, vous avez différentes options, dont certaines s'appliquent uniquement à l'une ou l'autre des deux langues que vous avez nommé. J'ai tendance à aimer votre offsetof() alternative, mais d'autres ont noté qu'en C++, il y a des types à qui offsetof ne peut pas être appliquée. En C, d'autre part, la norme décrit expressément son application à la structure des types, mais ne dit rien au sujet de l'union de types. Son comportement sur les types d'union, bien que très susceptibles d'être uniforme et naturel, techniquement pas défini.

En C, vous pouvez utiliser un littéral composé d'éviter le comportement non défini dans votre démarche:

#define HAS_MEMBER(T,X) (((T){0}).X, #X)

Qui fonctionne aussi bien sur la structure et les types d'union (si vous devez fournir un nom de type complet pour cette version, pas juste une balise). Son comportement est bien définie lorsque le type donné une membre. L'expansion viole une contrainte de la langue -- ce qui exige un diagnostic afin d'être émis, - lorsque le type n'a pas de membre, y compris lorsque ce n'est ni un type de structure, ni un type d'union.

Vous pouvez également utiliser sizeof, comme @alain a suggéré, parce que, bien que l' sizeof expression sera évaluée, son opérande ne sera pas évalué (à l'exception, dans C, lors de son opérande a variable-type modifié, qui ne s'appliquent pas à votre cas). Je pense que cette variation sera de travailler à la fois le C et le C++ sans introduire de comportement indéfini:

#define HAS_MEMBER(T,X) (sizeof(((T *)NULL)->X), #X)

J'ai de nouveau écrit de sorte qu'il fonctionne aussi bien sur les structures et les syndicats.

2voto

alain Points 1226

L'opérande gauche de l'opérateur virgule est un rebut de la valeur de l'expression

5 Expressions
11 Dans certains contextes, l'expression n'apparaît que pour ses effets secondaires. Une telle expression est appelé un jeté de la valeur de l'expression. L'expression est évaluée et sa valeur est ignorée. [...]

Il y a aussi des non évaluée opérandes qui, comme son nom l'indique, ne sont pas évaluées.

8 Dans certains contextes, non évaluée opérandes apparaissent (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). Non évalué opérande n'est pas évalué. Non évalué opérande est considéré comme une pleine expression. [...]

À l'aide d'un jeté de la valeur de l'expression dans votre cas d'utilisation est un comportement indéfini, mais non évaluée à l'aide d'un opérande n'est pas.

À l'aide de sizeof par exemple, ne devrait pas causer UB, car elle prend un opérande non évaluée.

#define STR_MEMBER(S,X) (sizeof(S::X), #X)

sizeof est préférable d' offsetofcar offsetof ne peut pas être utilisé pour les membres statiques et les classes qui ne sont pas standard-layout:

18 support de la Langue de la bibliothèque
4 La macro offsetof(type, états-indicateur) accepte une restriction de l' jeu de type d'arguments dans la présente Norme Internationale. Si le type n'est pas un standard-classe de mise en page (article 9), les résultats ne sont pas définis. [...] Le résultat de l'application de la offsetof macro à un champ est une donnée membre statique ou une fonction de membre n'est pas défini. [...]

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