Expressão + (+ k--) em C

9

Eu vi essa pergunta em um teste no qual temos que informar a saída do código a seguir.

#include<stdio.h>

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

A saída é -1. Não sei por que essa é a resposta.

O que a expressão +(+k--)significa em C?

Ankur Gautam
fonte
4
Foi formatado assim? Isso é mau ;-) #
Peter: Reinstate Monica
3
Dica: nunca escreva código BS como este na vida real.
Jabberwocky
5
Este não é UB. Veja minha resposta.
dbush
@ Peter-ReinstateMonica Sim, foi formatado assim.
Ankur Gautam
2
Por causa de sua natureza artificial e reviravoltas bizarras - k=k++é indefinido, mas não é indefinido porque nunca é executado por causa de uma condição ofuscada - estou votando para fechar como uma duplicata desta pergunta .
9788 Steve

Respostas:

10

[Para constar, editei esta resposta consideravelmente desde que ela foi aceita e votada. No entanto, ainda diz basicamente as mesmas coisas.]

Esse código é profundamente, talvez deliberadamente, confuso. Ele contém uma instância estritamente evitada do comportamento indefinido do pavor . É basicamente impossível determinar se a pessoa que construiu essa pergunta estava sendo muito, muito inteligente ou muito, muito estúpida. E a "lição" que esse código pretende ensinar ou questionar sobre você - a saber, que o operador unário plus não faz muito - certamente não é importante o suficiente para merecer esse tipo de desvio subversivo.

Existem dois aspectos confusos do código, a condição estranha:

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

e a declaração demente que controla:

k=k++;

Vou cobrir a segunda parte primeiro.

Se você tem uma variável como kessa que deseja incrementar em 1, C fornece não uma, não duas, três, mas quatro maneiras diferentes de fazer isso:

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

Apesar dessa recompensa (ou talvez por causa disso), alguns programadores ficam confusos e expulsam contorções como

k = k++;

Se você não conseguir descobrir o que isso deve fazer, não se preocupe: ninguém pode. Essa expressão contém duas tentativas diferentes de alterar ko valor (a k =parte e a k++parte) e, como não há regra em C para dizer qual das modificações tentadas "vence", uma expressão como essa é formalmente indefinida , significando não apenas que ele tem nenhum significado definido, mas que todo o programa que o contém é suspeito.

Agora, se você olhar com muito cuidado, verá que neste programa em particular, a linha k = k++não é realmente executada, porque (como estamos prestes a ver) a condição de controle é inicialmente falsa, então o loop é executado 0 vezes . Portanto, este programa em particular não pode realmente ser indefinido - mas ainda é patologicamente confuso.

Veja também essas respostas canônicas de SO a todas as perguntas relacionadas a Comportamento indefinido desse tipo.

Mas você não perguntou sobre o k=k++papel. Você perguntou sobre a primeira parte confusa, a +(+k--)!=0condição. Isso parece estranho, porque é estranho. Ninguém jamais escreveria esse código em um programa real. Portanto, não há razão para aprender a entender. (Sim, é verdade, explorar os limites de um sistema pode ajudá-lo a aprender sobre seus pontos positivos, mas há uma linha bastante clara em meu livro entre explorações imaginativas e instigantes e explorações abusivas e idiotas, e essa expressão está muito clara. o lado errado dessa linha.)

Enfim, vamos examinar +(+k--)!=0. (E depois disso, vamos esquecer tudo.) Qualquer expressão como essa deve ser entendida de dentro para fora. Eu presumo que você sabe o que

k--

faz. Ele pega ko valor atual e o "retorna" para o restante da expressão e diminui mais ou menos simultaneamente k, ou seja, armazena a quantidade k-1novamente k.

Mas então o que +faz? Isso é mais unário , e não binário. É como menos unário. Você sabe que binário menos subtrai: a expressão

a - b

subtrai b de a. E você sabe que menos unário nega as coisas: a expressão

-a

dá a você o negativo de a. O +que é unário é ... basicamente nada. +afornece ao valor de você , depois de alterar valores positivos para positivos e negativos para negativos. Então a expressão

+k--

dá a você o que k--lhe deu, ou seja, ko antigo valor.

Mas não terminamos, porque temos

+(+k--)

Isso apenas pega o que +k--lhe deu e se aplica unicamente +a ele novamente. Então isso lhe dá o que +k--lhe deu, o que lhe k--deu, que era ko antigo valor.

Então, no final, a condição

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

faz exatamente a mesma coisa que a condição muito mais comum

while(k-- != 0)

teria feito. (Ele também faz a mesma coisa que a condição de aparência ainda mais complicada while(+(+(+(+k--)))!=0)teria feito. E esses parênteses não são realmente necessários; ele também faz a mesma coisa que while(+ + + +k--!=0)teria feito.)

Mesmo descobrindo qual é a condição "normal"

while(k-- != 0)

faz é meio complicado. Há duas coisas acontecendo neste loop: Como o loop é executado potencialmente várias vezes, vamos:

  1. continuar fazendo k-- , para tornar kcada vez menor, mas também
  2. continue fazendo o corpo do loop, faça o que fizer.

Mas fazemos a k--parte imediatamente, antes (ou no processo de) decidir se devemos fazer outra viagem pelo circuito. E lembre-se de que k--"retorna" o valor antigo de k, antes de diminuí-lo. Nesse programa, o valor inicial ké 0. Portanto, k--"retornará" o valor antigo 0 e atualize kpara -1. Mas o restante da condição é != 0- mas, como acabamos de ver, na primeira vez que testamos a condição, obtivemos um 0. Portanto, não faremos nenhuma viagem pelo loop, portanto, não tentaremos executar o afirmação problemática k=k++.

Em outras palavras, nesse loop específico, embora eu tenha dito que "existem duas coisas acontecendo", acontece que a coisa 1 acontece uma vez, mas a coisa 2 acontece zero vezes.

De qualquer forma, espero que agora esteja bem claro por que essa desculpa ruim para um programa acaba imprimindo -1 como o valor final de k. Normalmente, não gosto de responder a perguntas do questionário como esta - parece trapaça -, mas neste caso, como discordo muito de todo o objetivo do exercício, não me importo.

Steve Summit
fonte
11
Exemplos planejados são típicos em qualquer classe fundamental. De que outra forma alguém escreveria uma pergunta sucinta sobre a precedência do operador em um exame? I ensinar matemática, e se eu tivesse um níquel para cada vez que um aluno perguntou: "Quando é que eu vou fazer isso em verdadeira vida?" ...
Scott
4
@ Scott Há artificial, e depois há artificial . Suponha que você seja um instrutor de treinamento de motorista. Suponha que você peça ao seu aluno que atravesse o tráfego pesado do centro da cidade enquanto bate na cabeça dele com um taco de beisebol. Indiscutivelmente, o aluno aprenderá uma habilidade potencialmente útil. Mas eu chamo isso de abuso. E defendo minha opinião de que o código nesta pergunta é abusivo, com valor pedagógico negativo.
9788 Steve Steve Summit
11
"Eu mantenho minha opinião", isso é justo, mas a palavra-chave aqui é "opinião". Uma resposta do StackOverflow talvez não seja o local ideal para expressar suas opiniões. :)
Scott
11
Para que conste, editei esta resposta consideravelmente desde que foi aceita e votada. Ainda diz basicamente as mesmas coisas, no entanto.
Steve Summit
11

À primeira vista, parece que esse código chama um comportamento indefinido, mas esse não é o caso.

Primeiro vamos formatar o código corretamente:

#include<stdio.h>

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

Então agora podemos ver que a declaração k=k++;está dentro do loop.

Agora vamos rastrear o programa:

Quando a condição do loop é avaliada pela primeira vez, kpossui o valor 0. A expressão k--tem o valor atual de k, que é 0, e ké diminuído como efeito colateral. Então, após esta declaração, o valor de ké -1.

O líder +dessa expressão não tem efeito no valor, então +k--avaliado como 0 e de forma semelhante+(+k--) como 0.

Então o !=operador é avaliado. Como 0!=0é falso, o corpo do loop não é inserido . Se o corpo tivesse sido inserido, você chamaria um comportamento indefinido porque k=k++lê e grava ksem um ponto de sequência. Mas o loop não é inserido, então não há UB.

Finalmente, o valor de ké impresso, que é -1.

dbush
fonte
11
É uma questão aberta, existencial, filosófica e imponderável, se o comportamento indefinido que não é executado não é indefinido. Não é indefinido? Acho que não.
9788 Steve
2
@SteveSummit Não há absolutamente nada existencial, filosófico, imponderável sobre isso. if (x != NULL) *x = 42;Isso é indefinido quando x == NULL? Claro que não. O comportamento indefinido não ocorre em partes do código que não são executadas. A palavra comportamento é uma dica. Código que não é executado não tem comportamento, indefinido ou não.
n. 'pronomes' m.
11
@SteveSummit Se o comportamento indefinido que não for executado invalidasse o programa inteiro, nenhum programa em C teria definido o comportamento. Porque os programas C são sempre apenas uma condição loop / if da execução do UB. Pegue uma iteração simples de matriz como exemplo: itera até que a verificação vinculada falhe. Executar a próxima iteração do loop seria um comportamento indefinido, mas como isso nunca é executado, está tudo bem.
Cmaster - reinstate monica 1/11/19
3
Não vou discutir sobre isso, porque não sou advogado de línguas. Mas aposto que se você fizesse essa pergunta e a classificasse como advogado da linguagem, poderia iniciar um debate animado. Observe que k=k++é qualitativamente diferente de *x=42. O último é bem definido se xfor um ponteiro válido, mas o primeiro é indefinido, não importa o quê. (Admito que você pode estar certo, mas, novamente, eu não vou discutir confinar, e eu estou cada vez mais preocupado temos sido magistralmente controlada.)
Steve Summit
11
"O último é bem definido se x é um ponteiro válido, mas o primeiro é indefinido, não importa o quê". Somente programas inteiros podem ter um comportamento indefinido. Esta noção não é aplicável a fragmentos de código tomados isoladamente.
n. 'pronomes' m.
3

Aqui está uma versão disso que mostra a precedência do operador:

+(+(k--))

Os dois +operadores unários não fazem nada, portanto essa expressão é exatamente equivalente a k--. A pessoa que escreveu isso provavelmente estava tentando mexer com sua mente.

SS Anne
fonte