2
votes

(A == 1 && a == 2 && a == 3) peut-il être évalué à vrai en C ou C ++?

c c++

1 commentaires

Les commentaires ne sont pas destinés à une discussion approfondie; cette conversation a été déplacée vers chat .


5 Réponses :


6
votes

Si a est de type primitif (c'est-à-dire que tous les opérateurs == et && sont intégrés) et que vous êtes dans un comportement défini, et il n'y a aucun moyen pour un autre thread de modifier un au milieu de l'exécution (c'est techniquement un cas de comportement indéfini - voir les commentaires - mais je l'ai laissé ici quand même car c'est l'exemple donné dans la question Java ), et il n'y a pas de magie du préprocesseur impliquée (voir la réponse choisie), alors je ne pense pas qu'il y ait quoi que ce soit pour que cela soit vrai. Cependant, comme vous pouvez le voir par cette liste de conditions, il existe de nombreux scénarios dans lesquels cette expression pourrait être évaluée à vrai, en fonction des types utilisés et du contexte du code.


13 commentaires

Si un thread pouvait modifier la valeur au milieu de l'exécution et que a est un type primitif, vous auriez un comportement indéfini, donc votre condition antérieure d'être dans un comportement défini couvre déjà cette condition.


@ FrançoisAndrieux Je vais supposer que vous êtes ici, je n'en sais pas assez sur UB pour contester.


@ FrançoisAndrieux Est-ce que cela s'applique aussi si a est volatile int (et écrit par un matériel externe)?


@MaxLanghof Non, mais dans ce cas, ce n'est pas un autre thread qui fait l'écriture et ne serait pas couvert par la condition redondante de toute façon. Modifier: dans le cas de l'utilisation de volatile pour essayer d'éliminer une condition de concurrence avec un autre thread plutôt qu'un périphérique matériel mappé, voir cette réponse sur les raisons pour lesquelles volatile n'empêche pas les conditions de course.


Modifier a en cours d'exécution n'est pas forcément UB. Voir ma réponse sur le cas volatile .


@EugeneSh. Je ne pense pas que les exemples qui dépendent des extensions soient des contre-exemples applicables pour une discussion sur le c ++ portable.


@ FrançoisAndrieux L'extension est de rendre l'exemple réel. Mais il démontre une situation qui ne contredit pas le standard C. volatile est standard et il est révélateur que la valeur de l'objet peut être modifiée par des voies inconnues de l'implémentation. Il ne précise pas comment créer une telle variable.


Volatile ne contribue pas à résoudre les conditions de course. 1 2 3 Toute solution où volatile fonctionne est soit une coïncidence, soit non portable. Edit: Liens externes: 4 5 < / a>.


@ FrançoisAndrieux Qui a dit portable? L'OP demande s'il existe une situation où l'expression peut être évaluée à vrai. Cette réponse énumère une condition - lorsque la valeur de a change entre les évaluations et indique qu'il s'agit de UB. Je dis - ce n'est pas nécessairement UB et montre pourquoi. Notez que ma réponse n'a rien à voir avec les fils.


isocpp.org lui-même renvoie à ce site humoristique < / a> pour souligner à quel point le mythe selon lequel volatile est répandu pour la sécurité des threads.


@EugeneSh. Et votre réponse est bonne pour cela. Mais cela nous ramène au problème initial que j'ai soulevé: "Je ne pense pas que les exemples qui dépendent des extensions soient des contre-exemples applicables pour une discussion sur le c ++ portable." Cette réponse ne fait aucune mention de spécificité plates-formes ou extensions, donc les contre-exemples qui reposent sur ceux-ci ne sont pas pertinents.


@ FrançoisAndrieux Encore une fois, l'affirmation entre parenthèses est incorrecte (et c'est mon seul point ici) sans aucune extension. Une fois qu'un a est volatil , il peut changer au milieu de l'exécution et il est autorisé par le standard C et n'est pas indéfini.


@EugeneSh. Mais il ne peut pas être changé par un autre thread, ce serait UB car il n'y a pas de synchronisation entre la lecture et l'écriture. Si une implémentation spécifique choisit de fournir des garanties supplémentaires, cela ne change pas que la norme elle-même ne change pas. Il ne serait pas raisonnable de s'attendre à devoir toujours ajouter "sauf si la plate-forme fournit des garanties pour cela" chaque fois qu'un comportement indéfini est mentionné. Tenir compte des promesses d'une plateforme spécifique ne parle plus du langage, mais d'une implémentation particulière du langage.



4
votes

En C, oui, c'est possible. Si a n'est pas initialisé alors (même s'il n'y a pas d'UB, comme indiqué ici ), son la valeur est indéterminée, la lire donne des résultats indéterminés, et la comparer à d'autres nombres donne donc également des résultats indéterminés.

En conséquence directe, a pourrait comparer true avec 1 en un instant, puis comparer true avec 2 au lieu de l'instant suivant. Il ne peut pas contenir ces deux valeurs simultanément, mais cela n'a pas d'importance, car sa valeur est indéterminée.

En pratique, je serais surpris de voir le comportement que vous décrivez, car il n'y a aucune raison réelle pour que le stockage réel change dans la mémoire entre les deux comparaisons.


En C ++, en quelque sorte. Ce qui précède est toujours vrai là-bas, mais la lecture d'une valeur indéterminée est toujours une opération non définie en C ++, donc vraiment tous les paris sont désactivés.

Les optimisations sont autorisées à bastardiser votre code de manière agressive, et lorsque vous faites des choses indéfinies, cela peut facilement entraîner toutes sortes de chaos.

Je serais donc moins surpris de voir ce résultat en C ++ qu'en C mais, si je le faisais, ce serait une observation sans but ni sens car un programme avec un comportement indéfini devrait de toute façon être totalement ignoré.


Et, naturellement, dans les deux langues, il y a des "trucs" que vous pouvez faire, comme #define a (x ++) , bien que cela ne semble pas être dans l'esprit de votre question.


10 commentaires

C'est un peu le point que j'essayais de faire valoir. Je comprends qu'en C, la valeur est fixée à une valeur initiale indéterminée, et contrairement à C ++, elle doit être auto-cohérente.


@ FrançoisAndrieux Les notions d'indéterminé et d'auto-cohérent sont totalement en contradiction, dans mon esprit, à moins que vous ne trouviez un libellé contraire. Indéterminé signifie indéterminé! Soit il y a une valeur déterministe, soit il n'y en a pas (même à part si nous sommes autorisés à l'observer!)


sa valeur est indéterminée, sa lecture donne des résultats indéterminés - pourquoi? Il a une certaine valeur, et il sera lu


@qrdl Non, il a une valeur indéterminée. C'est une autre façon anglaise de dire qu'il n'y a pas de valeur déterminée. Indéterminé ne veut pas dire «arbitraire», «aléatoire» ou «restant», cela signifie indéterminé !


@LightnessRacesinOrbit Il a une valeur indéterminée, mais accéder à cette valeur revient à accéder à toute autre valeur, le résultat est donc bien défini.


@qrdl Cela me semble être une déclaration absurde. Si c'était vrai, une valeur indéterminée serait comme une valeur déterminée, alors quel est l'intérêt de l'appeler indéterminée. Je suis désolé, mais je ne pense pas que vous soyez indéterminé. Mais je concède que mon expérience en droit du langage C est faible, alors peut-être que je manque quelque chose spécifique à C qui fait que «indéterminé» signifie «arbitraire mais déterminé» au lieu de, enfin, «indéterminé» ... Peut-être pourriez-vous m'aider en définissant «indéterminé» en termes C?


@LightnessRacesinOrbit Ok, je me suis trompé, j'ai trouvé ce que C11 en dit dans l'annexe J.2: Le comportement est indéfini dans les circonstances suivantes: [...] La valeur d'un objet à durée de stockage automatique est utilisée pendant il est indéterminé . Donc, accéder à une valeur indéterminée est en effet UB.


Il semble qu'il y ait eu un rapport d'anomalie à propos de ce. Il n'est pas clair pour moi si les développeurs C se plaignent de la nature des valeurs indéterminées ou demandent que la norme la clarifie, mais cela semble impliquer que les valeurs indéterminées en C sont comme des valeurs indéterminées en C ++ et ne sont pas obligées d'être soi-même -cohérent. Edit: La réponse du comité comprend: "La réponse à la question 1 est" oui ", une valeur non initialisée dans les conditions décrites peut sembler changer sa valeur." qui, à mon avis, fait assez autorité.


@qrdl C'est bizarre que le standard C ait cela et puis toute une autre partie sur la façon dont il n'est pas défini si la valeur non initialisée aurait pu être déclarée avec la classe de stockage de registre.


@qrdl Cool - mais je ne le prétendais toujours pas :) Nous pouvons avoir des résultats qui changent avec le temps sans avoir d'UB.



7
votes

Dépend de votre définition de "a est un entier":

int f(int b) { return b==1&&b==2&&b==3; }

Bien sûr:

int a__(){ static int r; return ++r; }
#define a a__()  //a is now an expression of type `int`
int main()
{
    return a==1 && a==2 && a==3; //returns 1
}

retournera toujours 0; et les optimiseurs remplaceront généralement la vérification par exactement cela.


0 commentaires

6
votes

Si nous mettons de côté la macro-magie, je vois une façon de répondre positivement à cette question. Mais cela nécessitera un peu plus que le simple C. Supposons que nous ayons une extension permettant d'utiliser l'attribut __attribute __ ((at (ADDRESS))); , qui place une variable dans une mémoire spécifique location (disponible dans certains compilateurs ARM par exemple, comme ARM GCC). Supposons que nous ayons un registre de compteur matériel à l'adresse ADDRESS , qui incrémente chaque lecture. Ensuite, nous pourrions faire quelque chose comme ceci:

volatile int a __attribute__((at(ADDRESS)));

Le volatile oblige le compilateur à générer le registre lu à chaque fois que la comparaison est effectuée, donc le compteur s'incrémentera de 3 fois. Si la valeur initiale du compteur est 1 , l'instruction retournera true.

P.S. Si vous n'aimez pas l'attribut at , le même effet peut être obtenu en utilisant le script de l'éditeur de liens en plaçant a dans une section de mémoire spécifique.


1 commentaires

C'est assez intelligent.



3
votes

Le programme suivant imprime aléatoirement vu: oui ou vu: non , selon que ce soit à un moment donné de l'exécution du thread principal (a == 0 && a == 1 && a == 2) évalué à true.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

_Atomic int a = 0;
_Atomic int relse = 0;

void *writer(void *arg)
{
  ++relse;
  while (relse != 2);
  for (int i = 100; i > 0; --i)
    {
      a = 0;
      a = 1;
      a = 2;
    }
  return NULL;
}

int main(void)
{
  int seen = 0;
  pthread_t pt;
  if (pthread_create(&pt, NULL, writer, NULL)) exit(EXIT_FAILURE);
  ++relse;
  while (relse != 2);
  for (int i = 100; i > 0; --i)
    seen |= (a == 0 && a == 1 && a == 2);
  printf("seen: %s\n", seen ? "yes":"no");
  pthread_join(pt, NULL);
  return 0;
}

Pour autant que je sache, cela ne contient pas d'indéfini comportement à tout moment, et a est de type entier, comme requis par la question. Évidemment, c'est une condition de concurrence, et donc si vu: oui ou vu: non est imprimé dépend de la plate-forme sur laquelle le programme est exécuté. Sous Linux, x86_64, gcc 8.2.1 les deux réponses apparaissent régulièrement. Si cela ne fonctionne pas, essayez d'augmenter les compteurs de boucles.


4 commentaires

Je me demande comment le comportement reposant sur le planificateur doit être classé.


@EugeneSh. Implémentation définie Je suppose. De plus, je n'ai pas testé cela sur une machine à processeur unique, donc je m'attends à ce qu'elle fonctionne en parallèle plutôt que simultanément.


Ouais, mais apparemment, c'est incohérent même sur la même implémentation (enfin, apparemment incohérente). Mais ce n'est pas indéfini, j'en suis presque sûr


@EugeneSh. Honnêtement, j'ai commencé avec des compteurs de boucles beaucoup plus élevés et j'ai été choqué par la cohérence avec laquelle la réponse vu: oui apparaissait. Je suis un peu surpris de la facilité avec laquelle cette course est à réaliser.