Trocando valor de variáveis

62 views
Skip to first unread message

Fabio Mazzarino

unread,
Jan 5, 2025, 11:18:27 AMJan 5
to ccppbrasil
Ah, trocar valores é muito fácil. De fato, é uma das primeiras coisas que aprendemos em programação.

Mas e se a restrição for não usar nenhuma variável temporária ou adicional? Aí já fica um pouco mais complicada.

Como trocar o valor de duas variáveis numéricas, sem utilizar variáveis temporárias?

A(s) solução(ões) está(ão) no Lab C++ de hoje.

Augusto Rodrigues

unread,
Jan 6, 2025, 5:35:24 AMJan 6
to ccppb...@googlegroups.com
Há muitíssimo tempo atrás, trabalhei com um rapaz que hoje nem sequer vive mais nesse pais (mudou-se definitivamente para o Canada), que mostrou como abordar esse problema com o uso do XOR ou operações de deslocamento de bits (acho)

Lembro que ele fez de uma maneira tão rápida que fiquei confuso e infelizmente não lembro como ele fez isso.

Procurei no chatgpt e encontrei o exemplo abaixo usando o XOR

#include <stdio.h>

int main() {
    int a, b;

    // Leitura dos valores
    printf("Digite o valor de a: ");
    scanf("%d", &a);
    printf("Digite o valor de b: ");
    scanf("%d", &b);

    // Troca usando XOR
    a = a ^ b;  // Passo 1: a agora é a XOR b
    b = a ^ b;  // Passo 2: b agora é (a XOR b) XOR b, que resulta em a
    a = a ^ b;  // Passo 3: a agora é (a XOR b) XOR a, que resulta em b

    // Exibindo os valores após a troca
    printf("Após a troca:\n");
    printf("a = %d\n", a);
    printf("b = %d\n", b);

    return 0;
}


O chatgpt reconhece que existe uma solução envolvendo deslocamento de bits (shift left and shift right), mas não forneceu um exemplo de código que demonstra isso, mesmo após algumas insistências.

Criar um site hoje em dia do jeito que você está fazendo é bem interessante, mas aviso que você tem um concorrente a altura e que ainda pode, com o passar do tempo, usufruir do seu conteúdo e publicá-lo sem o seu consentimento.
 
Mas se for um projeto pessoal que você queira desenvolver até o fim, então bom trabalho de divulgação!





--
http://ccppbrasil.github.io/
https://twitter.com/ccppbrasil
 
[&] C & C++ Brasil - http://www.ccppbrasil.org/
Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
---
Você recebeu essa mensagem porque está inscrito no grupo "ccppbrasil" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para ccppbrasil+...@googlegroups.com.
Para ver esta conversa, acesse https://groups.google.com/d/msgid/ccppbrasil/9f25ea25-8662-4224-b1c9-dbd48bc0e0f6n%40googlegroups.com.

Augusto Rodrigues

unread,
Jan 6, 2025, 5:51:53 AMJan 6
to ccppb...@googlegroups.com
Ah.. to falando do chatgpt e IA !! :-)

Entretanto, mesmo com essa concorrência desleal, continue o seu trabalho com esse site!! Foi apenas uma observação da minha parte sobre esse conflito entre IA e publicação de conteudo em sites de terceiros.

Bom inicio de ano !

Fabio A Mazzarino

unread,
Jan 6, 2025, 8:03:37 AMJan 6
to ccppb...@googlegroups.com
Augusto.

Como vc já sabe o site é colaborativo e eu sempre procuro publicar soluções propostas pelos leitores.

Vc se importa se eu publicar essa solução no post?

Obrigado pela colaboração.

--

Lab C++ - Código, Dicas e Snippets


Em seg., 6 de jan. de 2025 às 07:35, Augusto Rodrigues <guto.rodr...@gmail.com> escreveu:

Augusto Rodrigues

unread,
Jan 6, 2025, 9:15:06 AMJan 6
to ccppb...@googlegroups.com

Augusto Rodrigues

unread,
Jan 6, 2025, 9:16:05 AMJan 6
to ccppb...@googlegroups.com
  Pode sim.. foi o chat ge pe te que gerou a mesma. Eu apenas lembrei de algo dito a muito tempo atras que podia colaborar com o seu tópico. 


Em seg., 6 de jan. de 2025 às 10:03, Fabio A Mazzarino <fabio.m...@gmail.com> escreveu:

Fernando Mercês

unread,
Jan 6, 2025, 2:58:11 PMJan 6
to ccppb...@googlegroups.com
Isso me lembrou a instrução XCHG.

Exemplo no Visual Studio com inline assembly (32-bits):

int a = 10, b = 20;

__asm
{
   mov eax, a
   xchg eax, b
   mov a, eax
}

Mas há quem diga que é off-topic, já que não é mais C/C++. :)

PS.: Em Assembly puro somente a XCHG bastaria, claro.

Fabrício Cabral

unread,
Jan 6, 2025, 3:39:48 PMJan 6
to ccppb...@googlegroups.com
Pessoal,

Alguém me corrija se eu estiver falando alguma besteira, mas utilizar essas operações matemáticas não podem ocasionar um overflow ou underflow, dependendo dos valores armazenados em x ou y? Inclusive, pesquisei sobre isso na internet e encontrei a mesma afirmação nos comentários do link abaixo:


At.te.



"

--
http://ccppbrasil.github.io/
https://twitter.com/ccppbrasil
 
[&] C & C++ Brasil - http://www.ccppbrasil.org/
Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
---
Você recebeu essa mensagem porque está inscrito no grupo "ccppbrasil" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para ccppbrasil+...@googlegroups.com.
Para ver esta conversa, acesse https://groups.google.com/d/msgid/ccppbrasil/9f25ea25-8662-4224-b1c9-dbd48bc0e0f6n%40googlegroups.com.


--
--fx

Augusto Rodrigues

unread,
Jan 6, 2025, 3:45:41 PMJan 6
to ccppb...@googlegroups.com

Interessante...vou pesquisar isso depois.


Fernando Mercês

unread,
Jan 6, 2025, 4:51:30 PMJan 6
to ccppb...@googlegroups.com
Poder causar um estouro pode, mas se isso é um problema ou não, aí já é outra história. Neste código em específico, a meu ver, não é. Lembrando que em C, por padrão, o estouro de inteiros causa um comportamento indefinido, mas eu creio que a maioria dos compiladores implemente, neste caso, um wraparound (como traduz isso?), como ocorre com inteiros sem sinal, o que torna o comportamento definido. :)

Sendo assim, considerando a faixa de valores INT_MIN, ..., 0, ..., INT_MAX e o wraparound:

int a = INT_MAX, b = 1;

a = a + b; // a = INT_MAX + 1 causa estouro, mas o wraparound faz resultar em INT_MIN
b = a - b; // b = INT_MIN - 1 causa estouro de novo, mas o wraparound faz resultar em INT_MAX
a = a - b; // a = INT_MIN - INT_MAX = 1

No fim das contas, funciona. :)

No gcc dá pra controlar o comportamento do estouro de inteiros:

$ cat estouro.c
#include <stdio.h>
#include <limits.h>

int main() {
        int a = INT_MAX, b = 1;

        a = a + b;
        b = a - b;
        a = a - b;

        printf("a: %d, b: %d\n", a, b);
}

$ gcc estouro.c && ./a.out # sem especificar nada o compilador escolheu o wraparound
a: 1, b: 2147483647

$ gcc -fwrapv estouro.c && ./a.out # wraparound
a: 1, b: 2147483647

$ gcc -ftrapv estouro.c && ./a.out # trap (abortar)
Aborted

$ gcc -fsanitize=signed-integer-overflow estouro.c && ./a.out # aviso + wraparound
estouro.c:7:4: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
estouro.c:8:4: runtime error: signed integer overflow: -2147483648 - 1 cannot be represented in type 'int'
estouro.c:9:4: runtime error: signed integer overflow: -2147483648 - 2147483647 cannot be represented in type 'int'
a: 1, b: 2147483647

Referências:


Fabrício Cabral

unread,
Jan 6, 2025, 5:41:00 PMJan 6
to ccppb...@googlegroups.com
Fernando,

Mas a questão toda é que há um comportamento indefinido. Se o compilador faz ou não um 'wraparound', são outros 500, certo?

Assim sendo, você vê esta uma "solução" geral a ser utilizada para a troca valores entre duas variáveis?

At.te.



--
--fx

Fernando Mercês

unread,
Jan 6, 2025, 6:19:39 PMJan 6
to ccppb...@googlegroups.com
Opa,

>Mas a questão toda é que há um comportamento indefinido. Se o compilador faz ou não um 'wraparound', são outros 500, certo?
O comportamento não é definido pela especificação, o que permite que quem desenha o compilador o defina. Se eu tiver certeza de que o compilador definiu assim e meu projeto só usa ele, julgo seguro usar sim.

>Assim sendo, você vê esta uma "solução" geral a ser utilizada para a troca valores entre duas variáveis?
Acho que não. Depende do compilador. No entanto, é possível checar se haverá estouro aí tratar... Por exemplo, implementando o wraparound em código. Aí eu consideraria uma solução geral. :)

Só que não vejo como este requisito existiria na prática. Só em questão de prova/entrevista/curiosidade mesmo.

Abraço.

Augusto Rodrigues

unread,
Jan 7, 2025, 7:18:34 AMJan 7
to ccppb...@googlegroups.com
Opa.. o papo foi pra frente pelo jeito!

Eu quase li todo o conteudo do link do stack overflow fornecido pelo Fabricio Cabral. Dentre das respostas fornecidas pelos participantes do stack overflow gostei da resposta do usuario shobhit2905.

Agora em relação ao debate entre  Fabricio Cabral com Fernando Mercês, a melhor solução é garantir o workarount em código, pois dessa forma voce não fica na dependencia do comportamento que será definido pelo compilador, 
tornando o código multi-plataforma.

Mas fiquei com duas dúvidas sobre o código fornecido pelo Fernando:

1, É obrigatório que todos os compiladores puramente C atribua INT_MIN ao calcular o resultado da operação INT_MAX + 1 ? 

2, É obrigatório que todos os compiladores puramente C atribua INT_MAX ao calcular o resultado da operação INT_MIN - 1 ?

Pergunto isso porque somar qualquer número positivo a um valor limite MÁXIMO, ou seja INT_MAX, gera um valor MAXIMO que não pode ser armazenado em uma variavel do tipo int e
portanto o compilador pode atribuir a essa variavel o valor INT_MAX.

O mesmo raciocínio se aplica a subtrair qualquer valor positivo de INT_MIN porque o resultado dessa operação gera um valor MINIMO que não pode ser armazenado em uma variavel do tipo int e
portanto o compilador pode atribuir a essa variavel o valor INT_MIN.

Ou estaria errado essa interpretação ? Se sim, qual o motivo dessa interpretação estar errada ? Tem alguma documentação/norma que formalize como deve ser tratado as operações matemáticas sobre os valores INT_MAX e INT_MIN ?

Porque caso não esteja errado essa interpretação, o código proposto pelo Fernando geraria realmente resultados interessantes. :-0

Se assumirmos que 
* INT_MAX represente QUALQUER GRANDE VALOR que NÃO pode ser representado em uma variavel do tipo INT 
* INT_MIN represente QUALQUER PEQUENO VALOR que NÃO pode ser representado em uma variavel do tipo INT

O algoritmo proposto pelo Fernando geraria o seguinte resultado (alterei os comentários e os resultados para ilustrar o meu ponto de vista):

int a = INT_MAX, b = 1;
a = a + b; // a = INT_MAX + 1 ==> a = INT_MAX (porque somar qualquer numero positivo a um numero muito grande gera um numero muito grande)
b = a - b; // b = INT_MAX - 1 ==> b = INT_MAX  (porque subtrair qualquer numero positivo a um numero muito grande gera um numero muito grande)
a = a - b; // a = INT_MAX - 
INT_MAX ==> a = 0 (porque subtrair um valor por ele mesmo sempre gera 0)
Logo, a == 0 e b == INT_MAX ==> Não houve swap de forma correta, pois a deveria ser igual a 1

Tem treta se assumir que INT_MAX - 1 gere como resultado o valor igual a 2147483646 (resultado de 2147483647 - 1 ) porque o valor 2147483646 pode ser armazenado em uma variavel do tipo int.

int a = INT_MAX, b = 1;
a = a + b; // a = INT_MAX + 1 ==> a = INT_MAX (porque somar qualquer numero positivo a um numero muito grande gera um numero muito grande)
b = a - b; // b = INT_MAX - 1 ==> b = 2147483646 (porque é possivel armazenar o valor 2147483646 em uma variavel do tipo int)
a = a - b; // a = INT_MAX - (INT_MAX - 1) ==> a = 1 (porque INT_MAX == 2147483647 e INT_MAX - 1 == 214748364621474836472147483646 = 1)
Logo, a == 1 e b == 2147483646 ==> Não houve swap de forma correta, pois b deveria ser igual a 1

Fico aguardando os pitacos/opiniões/divergencias da galera sobre esse assunto.. hehehehe

Ah.. Visando limitar o escopo das futuras discussões, estou assumindo apenas swap entre variaveis do tipo inteiro de 32 bits.

Fui


Fabrício Cabral

unread,
Jan 7, 2025, 7:51:26 AMJan 7
to ccppb...@googlegroups.com
Augusto,

Eu concordo com você. Esse negócio de que "ah, já sei como o compilador X vai se comportar nesse caso", não me cheira bem, haja vista que o código pode ser compilado usando outro compilador com um comportamento diferente. Neste caso, ou a pessoa muda o código para utilizar três variáveis mesmo ou coloca um workaround no código. Aí a pergunta muda: será que vale a pena esse trabalho todo para economizar uma variável?

At.te.



--
--fx

Augusto Rodrigues

unread,
Jan 7, 2025, 9:13:42 AMJan 7
to ccppb...@googlegroups.com
Fabricio,

Bom.. a resposta para a sua pergunta é : Não, não vale a pena todo esse trabalho para economizar uma variável.

Porém o objetivo desses tipos de questionamentos/entrevistas feitas pelo recrutador é verificar as diversas competências que os entrevistados tem a oferecer em troca de um determinado salario.

Por exemplo, para estagiario ou junior, o recrutador faz esse desafio e tem dois entrevistados que fornecem as seguintes respostas:

Entrevistado Estagiario 1 responde --> Usa uma variavel auxiliar do tipo int para trocar os valores de duas variaveis do tipo int e fica com uma cara de feliz por ter fornecido a resposta correta 
em tempo recorde!! (alguns 10 a 30 segundos ou menos!!! - brilhante resposta!! )

Nota: O recrutador percebe que este estagiario esqueceu da restrição imposta pelo desafio (o que mostra que o estagiario possa ter problemas de interpretação ou de compreensão em relação 
ao que é solicitado para que ele faça, o que pode ser um problema para a empresa que esta ofertando a vaga de emprego).

Entrevistado Estagiario 2 responde --> Existem diversas abordagens citadas na comunidade de software e em outros ambientes (universidades, chats de discussão, etc) que mostram como 
altenar o valor de duas variaveis do tipo int sem uso de uma variavel auxiliar do mesmo tipo, com vantagens e desvantagens e etc etc etc. E acredito que a melhor solução para atender a
restrição imposta pelo desafio seja a abordagem XPTO porque etc etc etc etc.

Nota: O recrutador percebe que este estagiario:
[1] --> levou em consideração a restrição imposta pelo desafio, o que mostra que ele OUVE o que lhe é proposto, qualidade muito admirada nas empresas em relação a vagas 
de estagiarios, juniores, plenos, senior, especialista, etc.

[2] --> já conhecia esse desafio e estudou as abordagens existentes e discutidas nas diversas comunidades de software livre (universidade, chats de discussão, bebedeira entre os devs no 
boteco da esquina), se preparou antes mesmo de ouvir este desafio na entrevista, o que demonstra PRO-ATIVIDADE, e que procura meios para expandir seus conhecimentos mesmo 
fora do ambiente academico ou profissional (ou seja, mais pro-atividade).

[3] --> etc etc etc

[4] ---> Recrutador nem perde tempo, DISPENSA O ESTAGIARIO 1 e contrata o estagiario 2. :-D

Nota: O mesmo raciocinio acima é empregado pelos recrutadores para entrevistar junior, pleno, senior, especialista e etc. Claro.. com objetivos diferentes devido as exigencias de cada cargo.

E a gente discute para chegar mais preparado para as entrevistas! Ou para passar o tempo mesmo..  :-)

Fui


Fernando Mercês

unread,
Jan 7, 2025, 2:57:50 PMJan 7
to ccppb...@googlegroups.com
A conversa tá boa. :)

Augusto,

Não é obrigatório que o compilador faça wraparound (ele pode fazer trap por padrão - segundo a documentação do GCC, o hardware é um dos ditadores aqui), mas se o fizer, seja porque ele decidiu ou porque você forçou com a -fwrapv, aí o que é este wraparound é definido pela especificação sim. Na C23 [1], o wraparound de uma expressão é definido como resultado % pow(2, largura_do_tipo_em_bits). Então INT_MAX + 1 sempre dará INT_MIN sim (assumindo wraparound, claro). Imagino que seja o mesmo para versões anteriores da especificação, mas seria preciso checar.

Em tempo, quando você diz que INT_MAX + 1 não pode ser armazenado num signed int, há controvérsias. :) Isto porque INT_MAX + 1 na verdade "cabe" no int de 32-bits:

INT_MAX     == 2147483647 == 0b01111111111111111111111111111111
INT_MAX + 1 == 2147483648 == 0b10000000000000000000000000000000

Mas claro, isso é um estouro. No entanto, os bits são os mesmos para o -2147483648 (INT_MIN) e para o 2147483648 (INT_MAX + 1). Logo, do ponto de vista do compilador a solução de wraparound cabe bem porque ele só precisa "deixar rolar".

Não sei de um compilador que faça como você sugeriu (atribuir INT_MAX). Tentei gerar uns casos aqui com casts no GCC e ele sempre optou por wraparound mesmo.


Fabricio,

>Eu concordo com você. Esse negócio de que "ah, já sei como o compilador X vai se comportar nesse caso", não me cheira bem, haja vista que o código pode ser compilado usando outro compilador com um comportamento diferente
Eu discordo. Para mim tudo depende do requisito do projeto e suportar múltiplos compiladores é um dos requisitos. Veja, o GCC por exemplo oferece várias extensões que facilitam bastante a vida como VLAs e funções __builtin_*. Usá-las ou não (em favor da compatibilidade com outros compiladores) é uma decisão que, para mim, deve ser tomada ao desenhar [mentalmente] o projeto. Ou seja, não é um sim para todos os casos, nem um não para todos os casos. Mas claro, cada um pensa como quer. :)

> Neste caso, ou a pessoa muda o código para utilizar três variáveis mesmo ou coloca um workaround no código. Aí a pergunta muda: será que vale a pena esse trabalho todo para economizar uma variável?
Não. A gente está admitindo que existe o requisito de não utilizar três variáveis, que é o problema proposto. ;)

Abraços,
Fernando






Augusto Rodrigues

unread,
Jan 8, 2025, 9:04:49 AMJan 8
to ccppb...@googlegroups.com
Em ter., 7 de jan. de 2025 às 16:57, Fernando Mercês <nan...@gmail.com> escreveu:
A conversa tá boa. :)

Augusto,

Não é obrigatório que o compilador faça wraparound (ele pode fazer trap por padrão - segundo a documentação do GCC, o hardware é um dos ditadores aqui), mas se o fizer, seja porque ele decidiu ou porque você forçou com a -fwrapv, aí o que é este wraparound é definido pela especificação sim. Na C23 [1], o wraparound de uma expressão é definido como resultado % pow(2, largura_do_tipo_em_bits). Então INT_MAX + 1 sempre dará INT_MIN sim (assumindo wraparound, claro). Imagino que seja o mesmo para versões anteriores da especificação, mas seria preciso checar.

Bom.. se existe de fato uma especificação do que um wraparount deve fazer, então o correto é seguir essa especificação. Logo, caso seja fornecido a opção -fwrapv deve permitir que INT_MAX + 1 seja igual a INT_MIN e INT_MIN - 1 seja igual a INT_MAX.


Em tempo, quando você diz que INT_MAX + 1 não pode ser armazenado num signed int, há controvérsias. :) Isto porque INT_MAX + 1 na verdade "cabe" no int de 32-bits:

INT_MAX     == 2147483647 == 0b01111111111111111111111111111111
INT_MAX + 1 == 2147483648 == 0b10000000000000000000000000000000

Mas claro, isso é um estouro. No entanto, os bits são os mesmos para o -2147483648 (INT_MIN) e para o 2147483648 (INT_MAX + 1). Logo, do ponto de vista do compilador a solução de wraparound cabe bem porque ele só precisa "deixar rolar".

Para o compilador, realmente tanto faz. Ele pode inclusive entrar em loop somando um valor INT_MAX com o valor igual a 1 diversas vezes e não estourar por causa disso (de acordo com o ponto de vista do compilador).

Entretanto o compilador vai dessa forma produzir resultados não desejados gerando o que foi definido como "estouro".

 

Não sei de um compilador que faça como você sugeriu (atribuir INT_MAX). Tentei gerar uns casos aqui com casts no GCC e ele sempre optou por wraparound mesmo.


Fabricio,

Ok. No caso do compilador GCC certamente ele utilizou a abordagem que você citou anteriormente nos seus e-mails.

Eu tentei usar o raciocínio utilizado em notação assintótica, onde somar números sobre um número muito grande (INT_MAX) gera um número tão grande quanto INT_MAX e portanto o resultado final deveria ser igual a INT_MAX.

E apliquei o mesmo conceito para subtração de números de um número muito pequeno (INT_MIN),

Entretanto talvez realmente não seja aplicável esse tipo de ideia no mundo real dos compiladores :-)


>Eu concordo com você. Esse negócio de que "ah, já sei como o compilador X vai se comportar nesse caso", não me cheira bem, haja vista que o código pode ser compilado usando outro compilador com um comportamento diferente
Eu discordo. Para mim tudo depende do requisito do projeto e suportar múltiplos compiladores é um dos requisitos. Veja, o GCC por exemplo oferece várias extensões que facilitam bastante a vida como VLAs e funções __builtin_*. Usá-las ou não (em favor da compatibilidade com outros compiladores) é uma decisão que, para mim, deve ser tomada ao desenhar [mentalmente] o projeto. Ou seja, não é um sim para todos os casos, nem um não para todos os casos. Mas claro, cada um pensa como quer. :)


Sim. Você está correto. Na prática e no dia a dia dos devs é necessário produzir código que atenda as necessidades correntes do projeto naquele momento específico. E nem sempre é possível ou 
desejável produzir a solução perfeita. Na prática o cenário e o ambiente que o desenvolvedor vive na empresa é o que define o que será feito no software.
 
> Neste caso, ou a pessoa muda o código para utilizar três variáveis mesmo ou coloca um workaround no código. Aí a pergunta muda: será que vale a pena esse trabalho todo para economizar uma variável?
Não. A gente está admitindo que existe o requisito de não utilizar três variáveis, que é o problema proposto. ;)


Sim. Dentro do que foi proposto nesta discussão, é necessário respeitar os requisitos do desafio. 

Se isso vai ser útil ou não na vida do dia a dia dos devs, é outro cenário. Mas fica a dica: Tanto em desafios de entrevistador como no dia a dia dos devs é necessário respeitar
os requisitos, sejam eles coerentes ou não. 
 
Abraços,
Fernando



Abraços
Fui!!

 
Reply all
Reply to author
Forward
0 new messages