+ (+ k--) expression en C

9

J'ai vu cette question dans un test dans lequel nous devons dire la sortie du code suivant.

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
    k=k++;
    printf("%d\n", k);  
    return 0;
}

La sortie est -1. Je ne sais pas pourquoi c'est la réponse, cependant.

Que signifie l'expression +(+k--)en C?

Ankur Gautam
la source
4
Était-il formaté comme ceci? C'est méchant ;-)
Peter - Rétablir Monica
3
Astuce: n'écrivez jamais de code BS comme celui-ci dans la vraie vie.
Jabberwocky
5
Ce n'est pas UB. Voir ma réponse.
dbush
@ Peter-ReinstateMonica Oui, il a été formaté comme ceci.
Ankur Gautam
2
En raison de sa nature artificielle et de ses rebondissements bizarres - k=k++n'est pas défini, mais ce n'est pas indéfini car il n'est jamais exécuté en raison d'une condition obscurcie - je vote pour fermer en double de cette question .
Steve Summit

Réponses:

10

[Pour mémoire, j'ai modifié cette réponse de manière assez significative depuis qu'elle a été acceptée et mise aux voix. Cela dit toujours essentiellement les mêmes choses.]

Ce code est profondément, peut-être délibérément, déroutant. Il contient une instance étroitement évitée du comportement redouté non défini . Il est fondamentalement impossible de déterminer si la personne qui a construit cette question était très, très intelligente ou très, très stupide. Et la «leçon» que ce code pourrait prétendre vous enseigner ou vous interroger - à savoir que l'opérateur unaire plus ne fait pas grand-chose - n'est certainement pas assez importante pour mériter ce genre de mauvaise orientation subversive.

Il y a deux aspects confus du code, l'étrange condition:

while(+(+k--)!=0)

et la déclaration démente qu'il contrôle:

k=k++;

Je vais d'abord couvrir la deuxième partie.

Si vous avez une variable comme kcelle que vous souhaitez incrémenter de 1, C vous donne non pas une, pas deux, pas trois, mais quatre façons différentes de le faire:

  1. k = k + 1
  2. k += 1
  3. ++k
  4. k++

Malgré cette générosité (ou peut-être à cause de cela), certains programmeurs sont confus et crachent des contorsions comme

k = k++;

Si vous ne pouvez pas comprendre ce que cela est censé faire, ne vous inquiétez pas: personne ne le peut. Cette expression contient deux tentatives différentes de modifier kla valeur (la k =partie et la k++partie), et comme il n'y a pas de règle en C pour dire laquelle des tentatives de modification "gagne", une expression comme celle-ci est formellement indéfinie , ce qui signifie non seulement que il n'a pas de sens défini, mais que l'ensemble du programme qui le contient est suspect.

Maintenant, si vous regardez très attentivement, vous verrez que dans ce programme particulier, la ligne k = k++n'est pas exécutée, car (comme nous allons le voir) la condition de contrôle est initialement fausse, donc la boucle s'exécute 0 fois . Donc , ce programme ne pourrait pas réellement être indéfini - mais il est toujours déroutant pathologiquement.

Voir également ces réponses SO canoniques à toutes les questions concernant le comportement indéfini de ce type.

Mais vous n'avez pas posé de questions sur la k=k++partie. Vous avez posé des questions sur la première partie déroutante, la +(+k--)!=0condition. Cela semble étrange, car il est étrange. Personne n'écrirait jamais un tel code dans un vrai programme. Il n'y a donc aucune raison d'apprendre à le comprendre. (Oui, c'est vrai, explorer les limites d'un système peut vous aider à en savoir plus sur ses subtilités, mais il y a une ligne assez claire dans mon livre entre les explorations imaginatives et suscitant la réflexion contre les explorations stupides et abusives, et cette expression est très clairement sur du mauvais côté de cette ligne.)

Quoi qu'il en soit, examinons +(+k--)!=0. (Et après cela, oublions tout.) Toute expression comme celle-ci doit être comprise de l'intérieur. Je suppose que tu sais quoi

k--

Est-ce que. Il prend kla valeur actuelle de 'et la "retourne" au reste de l'expression, et il diminue plus ou moins simultanément k, c'est-à-dire qu'il stocke la quantité k-1dans k.

Mais alors qu'est-ce que ça +fait? C'est un plus unaire , pas un plus binaire. C'est comme un moins unaire. Vous savez que le binaire moins fait la soustraction: l'expression

a - b

soustrait b de a. Et vous savez que le moins unaire nie les choses: l'expression

-a

vous donne le négatif d'un. Ce que fait unaire, +c'est ... essentiellement rien. +avous donne ala valeur après avoir changé les valeurs positives en valeurs positives et négatives en négatives. Donc l'expression

+k--

vous donne tout ce k--qui vous a donné, c'est-à-dire kl'ancienne valeur.

Mais nous n'avons pas fini, car nous avons

+(+k--)

Cela prend tout ce +k--qui vous a été donné et s'applique +de nouveau à lui. Donc, cela vous donne tout ce qui vous a +k--donné, ce qui k--vous a donné, qui était kl'ancienne valeur.

Donc à la fin, la condition

while(+(+k--)!=0)

fait exactement la même chose que la condition beaucoup plus ordinaire

while(k-- != 0)

aurait fait. (Il fait également la même chose que l' while(+(+(+(+k--)))!=0)aurait fait une condition d'apparence encore plus compliquée . Et ces parenthèses ne sont pas vraiment nécessaires; il fait également la même chose que l' while(+ + + +k--!=0)aurait fait.)

Même comprendre ce que la condition "normale"

while(k-- != 0)

est un peu délicat. Il y a en quelque sorte deux choses qui se passent dans cette boucle: Comme la boucle s'exécute potentiellement plusieurs fois, nous allons:

  1. continuer à faire k--, pour faire de kplus en plus petit, mais aussi
  2. continuez à faire le corps de la boucle, quoi que cela fasse.

Mais nous faisons la k--partie tout de suite, avant (ou en train de) décider de faire un autre voyage à travers la boucle. Et rappelez-vous que k--"renvoie" l'ancienne valeur de k, avant de la décrémenter. Dans ce programme, la valeur initiale de kest 0. Il k--va donc "retourner" l'ancienne valeur 0, puis mettre kà jour à -1. Mais le reste de la condition est != 0- mais comme nous venons de le voir, la première fois que nous avons testé la condition, nous avons obtenu un 0. Donc, nous ne ferons aucun voyage dans la boucle, donc nous n'essaierons pas d'exécuter le déclaration problématique k=k++du tout.

En d'autres termes, dans cette boucle particulière, même si j'ai dit "qu'il se passe en quelque sorte deux choses", il s'avère que la chose 1 se produit une fois, mais la chose 2 se produit zéro fois.

En tout cas, j'espère qu'il est maintenant suffisamment clair pourquoi cette mauvaise excuse pour un programme finit par imprimer -1 comme valeur finale de k. Normalement, je n'aime pas répondre à des questions de quiz comme celle-ci - cela ressemble à de la triche - mais dans ce cas, comme je suis tellement bruyamment en désaccord avec le but de l'exercice, cela ne me dérange pas.

Sommet Steve
la source
1
Les exemples artificiels sont typiques dans n'importe quelle classe fondamentale. Sinon, comment rédiger une question succincte sur la priorité des opérateurs à un examen? J'enseigne les mathématiques, et si j'avais un nickel à chaque fois qu'un étudiant demandait "Quand vais-je faire ça dans la vraie vie?" ...
Scott
4
@Scott Il y a artificiel, et puis il y a artificiel . Supposons que vous soyez instructeur de conduite. Supposons que vous demandiez à votre élève de traverser la circulation dense du centre-ville tout en le frappant à la tête avec une batte de baseball. On peut dire que l'étudiant apprendra une compétence potentiellement utile. Mais j'appelle cela un abus. Et je maintiens mon opinion que le code de cette question est abusif, avec une valeur pédagogique négative.
Steve Summit,
1
"Je maintiens mon opinion", c'est juste, mais le mot-clé ici est "opinion". Une réponse StackOverflow n'est peut-être pas l'endroit idéal pour exprimer ses opinions. :)
Scott
1
Pour mémoire, j'ai modifié cette réponse de manière assez significative depuis qu'elle a été acceptée et votée. Cela dit toujours essentiellement les mêmes choses.
Steve Summit,
11

À première vue, il semble que ce code invoque un comportement non défini, mais ce n'est pas le cas.

Formater d'abord le code correctement:

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
        k=k++;
    printf("%d\n", k);  
    return 0;
}

Alors maintenant, nous pouvons voir que l'instruction k=k++;est à l'intérieur de la boucle.

Voyons maintenant le programme:

Lorsque la condition de boucle est évaluée pour la première fois, ka la valeur 0. L'expression k--a la valeur actuelle de k, qui est 0, et kest décrémentée comme effet secondaire. Donc, après cette déclaration, la valeur de kest -1.

Le début +de cette expression n'a aucun effet sur la valeur, donc +k--évalué à 0 et de même +(+k--)à 0.

Ensuite, l' !=opérateur est évalué. Puisque 0!=0est faux, le corps de la boucle n'est pas entré . Si le corps avait été entré, vous invoqueriez un comportement indéfini car les k=k++deux lit et écrit ksans point de séquence. Mais la boucle n'est pas entrée, donc pas d'UB.

Enfin la valeur de kest imprimée qui est -1.

dbush
la source
1
C'est une question ouverte, existentielle, philosophique, impondérable, si un comportement indéfini qui n'est pas exécuté n'est pas indéfini. N'est-ce pas indéfini? Je ne pense pas.
Steve Summit,
2
@SteveSummit Il n'y a absolument rien d'existentiel, de philosophique, d'impondérable à ce sujet. if (x != NULL) *x = 42;Est-ce indéfini quand x == NULL? Bien sûr que non. Un comportement indéfini ne se produit pas dans les parties de code qui ne sont pas exécutées. Le mot comportement est un indice. Le code qui n'est pas exécuté n'a aucun comportement, indéfini ou autre.
n. «pronoms» m.
1
@SteveSummit Si un comportement non défini qui n'est pas exécuté invaliderait le programme entier, aucun programme C n'aurait un comportement défini. Parce que les programmes C sont toujours juste une boucle / condition loin de l'exécution de UB. Prenons une simple itération de tableau pour un exemple: elle itère jusqu'à ce que la vérification des limites échoue. L'exécution de l'itération de boucle suivante serait un comportement indéfini, mais comme cela n'est jamais exécuté, tout va bien.
cmaster - réintègre monica
3
Je ne vais pas en discuter, car je ne fais pas de juriste linguistique. Mais je parie que si vous posiez cette question et l'étiquetiez langue-avocat, vous pourriez lancer un débat animé. Notez que k=k++c'est qualitativement différent de *x=42. Ce dernier est bien défini si xest un pointeur valide, mais le premier n'est pas défini quoi qu'il arrive. (Je concède que vous avez peut-être raison, mais encore une fois, je ne vais pas m'opposer à cela, et je suis de plus en plus préoccupé par le fait que nous ayons été magistralement contrôlés.)
Steve Summit
1
"Ce dernier est bien défini si x est un pointeur valide, mais le premier n'est pas défini quoi qu'il arrive". Seuls des programmes entiers peuvent avoir un comportement non défini. Cette notion n'est pas applicable aux fragments de code pris isolément.
n. «pronoms» m.
3

Voici une version de cela qui montre la priorité des opérateurs:

+(+(k--))

Les deux +opérateurs unaires ne font rien, donc cette expression est exactement équivalente à k--. La personne qui a écrit ceci essayait probablement de jouer avec votre esprit.

SS Anne
la source