Desenvolvimento - C/C++

O que acontece quando o contador estoura

Dois conceitos de programação relacionados a limites computacionais são bem conhecidos do programador: o famigerado overflow e o não-tão-famoso underflow (embora seja fácil imaginar que ele é o oposto do primeiro).

por Wanderley Caloni Jr



Dois conceitos de programação relacionados a limites computacionais são bem conhecidos do programador: o famigerado overflow e o não-tão-famoso underflow (embora seja fácil imaginar que ele é o oposto do primeiro). O primeiro ocorre quando somamos à uma variável inteira não-nula um valor cujo resultado não consegue ser representado pelo tamanho de memória usado para armazenar esse tipo inteiro (que pode ser um caractere, um inteiro curto, inteiro longo e por aí vai). O underflow, por outro lado (outro lado mesmo), é o resultado de uma subtração que não pode ser representado pelo número de bits do seu tipo inteiro.

Nada melhor que um código para ilustrar melhor esses dois acontecimentos ilustres:

#include <limits.h>
#include <iostream>

int main()
{
   int x = INT_MAX; // máximo inteiro que pode ser armazenado no tipo int

   std::cout << x << std::endl; // se é o máximo, é um valor positivo
   x = x + 1;  // mas basta um empurrãozinho para que
   std::cout << x << std::endl; // a casa caia
}

Saída
=====
2147483647
-2147483648

O indicador de que algo está errado é simples: como diabos foi um número positivo virar negativo, já que eu somei ao invés de subtrair? No entanto, computacionalmente parece extremamente correto: o próximo número após o maior valor positivo possível é o menor número negativo possível.

Complemento de dois

Nos computadores atuais tudo no final acaba sendo representado por zeros e uns. Até o sinal de negativo dos números menores que zero. Por isso mesmo, para que consigamos usar números menores que zero, precisamos gastar um bit para indicar que este número é negativo. Existem muitas representações interessantes, dentre as quais a mais popular acabou sendo a de complemento de dois. A regra é simples:

<q>Toda representação binária que tiver o bit mais significativo ligado (o bit mais à esquerda) significa um número negativo cujo valor absoluto se obtém invertendo-se o resto dos bits e adicionando um.</q>

Quando o bit mais à esquerda não está ligado o valor absoluto é ele mesmo; ou seja, é um número positivo, incluindo o zero. Como vamos ver, isso facilita em muito os cálculos para o computador. Para nós, a coisa não fica lá muito difícil. Só precisamos lembrar que, em hexadecimal, todos os valores que tiverem o byte mais significativo igual ou maior que 8 (que é 1000 em binário) é negativo e temos que aplicar o método de complemento de dois para obter seu valor absoluto. Vejamos o valor -8, por exemplo:

  1. Primeiro temos a representação real (em um byte): 1111 1000.
  2. O bit mais significativo está ligado: é um número negativo. Descartamos o sinal, fica 111 1000.
  3. Devemos agora inverter todos os bits: 111 1000 se torna 000 0111.
  4. Por fim, somamos um: 000 0111 + 1 = 000 1000.
  5. Como vimos no parágrafo anterior, 000 1000, ou simplesmente 1000, é 8. Na verdade, -8!

Interessante: o que significa, na notação complemento de dois, a representação onde estão todos os bits ligados, independente do número de bytes?

Agora fica fácil (pelo menos deveria)

Se alterarmos o código acima para imprimir na saída os números hexadecimais, obteremos a seguinte saída:

7fffffff
80000000

E o mais legal é que agora sabemos que o primeiro número é o maior valor positivo possível nesse tamanho de int, pois possui todos os bits ligados exceto o bit de sinal. Já o segundo número, o primeiro incrementado de 1, possui todos os bits desligados exceto o bit de sinal: é o menor número negativo possível!

Para pensar: consegue imaginar como os cálculos são feitos pelo computador? Curioso? Então <a href="http://en.wikipedia.org/wiki/Twos_complement">dê uma olhada</a>.

Artigo original retirado de "O que acontece quando o contador estoura", por Wanderley Caloni.

Wanderley Caloni Jr

Wanderley Caloni Jr