web-dev-qa-db-fra.com

Pourquoi cette version de AND logique en C n'affiche-t-elle pas un comportement de court-circuit?

Oui, c'est une question de devoirs, mais j'ai fait mes recherches et beaucoup de réflexion approfondie sur le sujet et je ne peux pas comprendre cela. La question indique que ce morceau de code ne présente PAS comportement de court-circuit et demande pourquoi. Mais il me semble qu'il présente un comportement de court-circuit, alors quelqu'un peut-il expliquer pourquoi ce n'est pas le cas?

En C:

int sc_and(int a, int b) {
    return a ? b : 0;
}

Il me semble que dans le cas où a est faux, le programme n'essaiera pas du tout d'évaluer b, mais je dois me tromper. Pourquoi le programme touche-t-il même b dans ce cas, alors qu'il n'est pas obligé de le faire?

79
Bobazonski

C'est une question piège. b est un argument d'entrée de la méthode sc_and et sera donc toujours évalué. En d'autres termes, sc_and(a(), b()) appellera a() et appellera b() (ordre non garanti), puis appelez sc_and Avec les résultats de a(), b() qui passe à a?b:0. Cela n'a rien à voir avec l'opérateur ternaire lui-même, qui court-circuiterait absolument.

MISE À JOUR

En ce qui concerne la raison pour laquelle j'ai appelé cela une "question piège": c'est en raison du manque de contexte bien défini pour savoir où envisager le "court-circuit" (du moins tel que reproduit par l'OP). Beaucoup de personnes, quand on leur donne juste une définition de fonction, supposons que le contexte de la question pose sur le corps de la fonction ; souvent, ils ne considèrent pas la fonction comme une expression en soi. C'est le "truc" de la question; Pour vous rappeler qu'en programmation en général, mais surtout dans les langages comme les C-likes qui ont souvent de nombreuses exceptions aux règles, vous ne pouvez pas faire ça. Exemple, si la question a été posée comme telle:

Considérez le code suivant. Sc_and présente un comportement de court-circuit lors de son appel depuis main :

int sc_and(int a, int b){
    return a?b:0;
}

int a(){
    cout<<"called a!"<<endl;
    return 0;
}

int b(){
    cout<<"called b!"<<endl;
    return 1;
}

int main(char* argc, char** argv){
    int x = sc_and(a(), b());
    return 0;
}

Il serait immédiatement clair que vous êtes censé considérer sc_and Comme un opérateur en soi dans votre propre langage spécifique au domaine, et évaluer si le l'appel à sc_and présente un comportement de court-circuit comme le ferait normalement &&. Je ne considérerais pas cela comme une question piège, car il est clair que vous n'êtes pas censé vous concentrer sur l'opérateur ternaire, et que vous êtes plutôt censé vous concentrer sur la mécanique d'appel de fonction de C/C++ (et, je suppose, conduire joliment dans une question de suivi pour écrire un sc_and qui fait un court-circuit, ce qui impliquerait d'utiliser un #define plutôt qu'une fonction).

Que vous appeliez ou non ce que l'opérateur ternaire lui-même fait de court-circuit (ou autre chose, comme `` évaluation conditionnelle '') dépend de votre définition du court-circuit, et vous pouvez lire les différents commentaires pour y réfléchir. Par moi, c'est le cas, mais ce n'est pas très pertinent pour la question réelle ou pourquoi je l'ai appelé un "truc".

117
aruisdante

Lorsque la déclaration

bool x = a && b++;  // a and b are of int type

exécute, b++ ne sera pas évalué si l'opérande a est évalué à false (comportement de court-circuit). Cela signifie que l'effet secondaire sur b n'aura pas lieu.

Maintenant, regardez la fonction:

bool and_fun(int a, int b)
{
     return a && b; 
}

et appelle ça

bool x = and_fun(a, b++);

Dans ce cas, que a soit true ou false, b++ sera toujours évalué pendant l'appel de fonction et l'effet secondaire sur b aura toujours lieu.

Il en va de même pour

int x = a ? b : 0; // Short circuit behavior 

et

int sc_and (int a, int b) // No short circuit behavior.
{
   return a ? b : 0;
} 
42
haccks

Comme déjà souligné par d'autres, peu importe ce qui est passé dans la fonction comme les deux arguments, elle est évaluée au fur et à mesure qu'elle est transmise. C'est bien avant l'opération tenary.

D'un autre côté, ce

#define sc_and(a, b) \
  ((a) ?(b) :0)

serait "court-circuit", car cette macro n'implique pas un appel de fonction et avec cela aucune évaluation de l'argument d'une fonction (s) est effectuée.

19
alk

Modifié pour corriger les erreurs notées dans le commentaire @cmasters.


Dans

int sc_and(int a, int b) {
    return a ? b : 0;
}

... l'expression returned présente une évaluation de court-circuit, mais pas l'appel de fonction.

Essayez d'appeler

sc_and (0, 1 / 0);

L'appel de fonction évalue 1 / 0, bien qu'il ne soit jamais utilisé, ce qui provoque - probablement - une erreur de division par zéro.

Des extraits pertinents de la (version préliminaire) norme ANSI C sont:

2.1.2.3 Exécution du programme

...

Dans la machine abstraite, toutes les expressions sont évaluées comme spécifié par la sémantique. Une implémentation réelle n'a pas besoin d'évaluer une partie d'une expression si elle peut en déduire que sa valeur n'est pas utilisée et qu'aucun effet secondaire nécessaire n'est produit (y compris ceux provoqués par l'appel d'une fonction ou l'accès à un objet volatil).

et

3.3.2.2 Appels de fonction

....

Sémantique

...

Lors de la préparation de l'appel à une fonction, les arguments sont évalués et chaque paramètre reçoit la valeur de l'argument correspondant.

Je suppose que chaque argument est évalué comme une expression, mais que la liste des arguments dans son ensemble n'est pas pas une expression, donc le comportement non SCE est obligatoire.

En tant que plongeur à la surface des eaux profondes de la norme C, j'apprécierais une vue correctement informée sur deux aspects:

  • Est-ce que l'évaluation 1 / 0 produit un comportement indéfini?
  • Une liste d'arguments est-elle une expression? (Je crois que non)

P.S.

Même vous passez en C++ et définissez sc_and en tant que fonction inline, vous n'obtiendrez pas SCE. Si vous la définissez comme une macro C, comme le fait @ alk , vous le ferez certainement.

5
Thumbnail

Pour voir clairement les courts-circuits op ternaires, essayez de changer légèrement le code pour utiliser des pointeurs de fonction au lieu d'entiers:

int a() {
    printf("I'm a() returning 0\n");
    return 0;
}

int b() {
    printf("And I'm b() returning 1 (not that it matters)\n");
    return 1;
}

int sc_and(int (*a)(), int (*b)()) {
    a() ? b() : 0;
}

int main() {
    sc_and(a, b);
    return 0;
}

Et puis compilez-le (même avec presque AUCUNE optimisation: -O0!). Vous verrez que b() n'est pas exécutée si a() retourne false.

% gcc -O0 tershort.c            
% ./a.out 
I'm a() returning 0
% 

Ici, l'assembly généré ressemble à ceci:

    call    *%rdx      <-- call a()
    testl   %eax, %eax <-- test result
    je      .L8        <-- skip if 0 (false)
    movq    -16(%rbp), %rdx
    movl    $0, %eax
    call    *%rdx      <- calls b() only if not skipped
.L8:

Ainsi, comme d'autres l'ont souligné à juste titre, l'astuce consiste à vous concentrer sur le comportement de l'opérateur ternaire qui FAIT un court-circuit (appelez cette `` évaluation conditionnelle '') au lieu de l'évaluation des paramètres sur appel (appel par valeur) qui ne court-circuite pas.

4
flaviodesousa

L'opérateur ternaire C ne peut jamais court-circuiter, car il n'évalue qu'une seule expression a (la condition), pour déterminer une valeur donnée par des expressions b et c , si une valeur peut être retournée.

Le code suivant:

int ret = a ? b : c; // Here, b and c are expressions that return a value.

Il est presque équivalent au code suivant:

int ret;
if(a) {ret = b} else {ret = c}

L'expression a peut être formée par d'autres opérateurs comme && ou || qui peut court-circuiter car ils peuvent évaluer deux expressions avant de renvoyer une valeur, mais cela ne serait pas considéré comme l'opérateur ternaire effectuant un court-circuit mais comme les opérateurs utilisés dans la condition comme dans une instruction if régulière.

Mise à jour:

Il y a un débat sur le fait que l'opérateur ternaire est un opérateur de court-circuit. L'argument dit que tout opérateur qui n'évalue pas tous ses opérandes court-circuite selon @aruisdante dans le commentaire ci-dessous. Si on donnait cette définition, alors l'opérateur ternaire court-circuiterait et dans le cas c'est la définition originale je suis d'accord. Le problème est que le terme "court-circuit" était à l'origine utilisé pour un type spécifique d'opérateur qui permettait ce comportement et ce sont les opérateurs logiques/booléens, et la raison pour laquelle ce ne sont que ceux-là est ce que j'essaierai d'expliquer.

À la suite de l'article Évaluation de court-circuit , l'évaluation de court-circuit n'est référée qu'aux opérateurs booléens implémentés dans le langage d'une manière où sachant que le premier opérande rendra le second non pertinent, c'est-à-dire pour le L'opérateur && étant le premier opérande false , et pour le || étant le premier opérande vrai , la spécification C11 le note également dans 6.5.13 Opérateur ET logique et 6.5.14 Logique OR.

Cela signifie que pour que le comportement de court-circuit soit identifié, vous devez vous attendre à l'identifier dans un opérateur qui doit évaluer tous les opérandes, tout comme les opérateurs booléens si le premier opérande ne rend pas inutile le second. Ceci est conforme à ce qui est écrit dans une autre définition du court-circuit dans MathWorks sous la section "Court-circuitage logique", car le court-circuit vient des opérateurs logiques.

Comme j'ai essayé d'expliquer l'opérateur ternaire C, également appelé ternaire si, n'évalue que deux des opérandes, il évalue le premier, puis évalue un second, l'un des deux restant en fonction de la valeur de la Premier. Il fait toujours cela, il n'est pas censé évaluer les trois dans toutes les situations, il n'y a donc pas de "court-circuit" dans tous les cas.

Comme toujours, si vous voyez que quelque chose ne va pas, veuillez écrire un commentaire avec un argument contre cela et pas seulement un downvote, cela ne fait qu'empirer l'expérience SO, et je crois que nous pouvons être un une bien meilleure communauté que celle qui ne fait que voter en aval répond à laquelle on n'est pas d'accord.

0
Adrián Pérez