sexta-feira, 30 de maio de 2014

Erro silencioso na instalação do client 11.2.0.4 x64 para Windows 7

Eu estou montando um ambiente de testes com algumas máquinas virtuais rodando Oracle 11.2.0.4 e me deparei com um erro estranho ao instalar o Oracle client x86-64 no Windows 7: o instalador simplesmente fecha após o termino da checagem de pré-requisitos sem mensagem alguma.

Após investigar um pouco na internet acabei descobrindo que mais pessoas tiveram problemas similares e parece estar relacionado com o ambiente JRE que vem embutido no instalador. A solução para mim foi utilizar um parâmetro do executável do setup para escolher outro ambiente JRE:

C:\oracle\winx64_11g_client>setup -jreLoc c:\sqldeveloper\jdk

No caso, utilizei a JDK que vem embutida na última versão do SQL Developer (4.0.2) e aí a instalação funcionou normalmente. Cabe aqui pontuar que testei outras duas JREs que eu tenho instaladas na mesma máquina (1.6.0-71 e 1.7.0-51) e nenhuma das duas funcionou. Como eu não sou especialista em java não sei o que exatamente fez a diferença, mas fica aí o alerta e o workaround.

terça-feira, 19 de novembro de 2013

Matemática de Ponteiros

Hoje vou tratar de um tema que aflinge a maioria dos programadores iniciantes em C e C++: os malvados ponteiros. O objetivo é fazer uma revisão do assunto tratando algumas propriedades que podem passar despercebidas do iniciado, mas que fazem toda a diferença na hora de compreender aquele "bug maluco" que aparece de vez enquando. Entender a fundo como funcionam os ponteiros pode ser a diferença entre passar dias ou meses debugando um código para nunca achar aquele erro, ou escrever um código menos suceptível a erros logo de cara.

Bom, para começar, o que é um ponteiro afinal? Um ponteiro nada mais é do que uma variável que contém um endereço de memória. O que muda de ponteiro para ponteiro é o significado deste endereço. 

O ponteiro é representado pelo símbolo asterísco (*). Também vamos utilizar bastante o operador de endereço (&), que retorna o endereço de memória de uma variável.

Vamos começar com alguns exemplos básicos para ilustrar:

#include <iostream>

using namespace std;

int main()
{
  int  a = 0; // Declaração de uma variável tipo int
  int  *p;    // Declaraçào de um ponteiro para uma variável inteira
  p = &a;     // Atribui a p o endereço de a

  cout << " a = " <<  a << "\t\t *p = " << *p << endl;
  cout << "&a = " << &a << "\t  p = "   <<  p << endl;
  cout << endl;

  return 0;
}

A saída esperada deste programa é a seguinte (os endereços vão variar de máquina para máquina):

 a = 0           *p = 0
&a = 0x28fef4     p = 0x28fef4

O objetivo deste exemplo é mostrar a operação normal com ponteiros. Na primeira parte, declaramos uma variável inteira "a" inicializada com o valor zero e um ponteiro "p" para um inteiro. Na seqüência, fazemos a atribuição do endereço de "a" ao ponteiro "p". Em seguida, utilizamos a saída padrão para mostrar que "p" e "a" são duas visões do mesmo dado, dadas as seguintes ressalvas:

1) para enxergarmos o valor armazenado em "a", basta referenciar a variável pelo nome.
2) para enxergarmos o endereço de "a", precisamos utilizar o operador de endereço &.
3) para enxergarmos o valor que "p" aponta, precisamos desreferenciar o ponteiro, com o operador *.
4) para enxergarmos o endereço contido em "p", basta referenciar "p" pelo nome.

Note que, na observação 4, falamos sobre o "endereço contido em p", e não "o endereço de p". Eu fiz essa diferenciação de propósito, pois "p" também é uma variável que está armazenada no seu próprio endereço. Vamos ver o exemplo modificado:

int main()
{
  int  a = 0; // Declaração de uma variável tipo int
  int  *p;    // Declaraçào de um ponteiro para uma variável inteira
  p = &a;     // Atribui a p o endereço de a

  cout << " a = " <<  a << "\t\t *p = " << *p << endl;
  cout << "&a = " << &a << "\t  p = "   <<  p << endl;
  cout << endl;

  cout << "&p = " << &p << endl;

  return 0;
}

Observe a saída:

 a = 0           *p = 0
&a = 0x28fefc     p = 0x28fefc

&p = 0x28fef8

Basicamente, o valor de "p" é o endereço de "a" (0x28fefc), e o endereço de "p" (0x28fef8) é o local da memória onde está guardado este valor.

Além disso, quando declaramos "p" não atribuímos nenhum valor a ele. Assim como qualquer outra variável declarada sem valor, ele irá conter neste momento um "lixo" qualquer. Considerando que este lixo pode ser um endereço válido, usar este ponteiro sem atribuir-lhe o valor correto pode ter conseqüências catastróficas como os lendários bugs não-determinísticos. Por este motivo, é uma boa prática ao declarar ponteiros sem valor atribuir-lhe o valor zero ou NULL (NULL = 0), pois os sistemas operacionais modernos são capazes de lançar exceções (null pointer exception) quando tentamos acessar o endereço zero, mas eles não necessariamente conseguem diferenciar um endereço "lixo" válido de um endereço "bom" e, acredite, é muito melhor varrer o código em busca de um null pointer exception do que de um erro aleatório.

int *p = NULL; // Sempre!

Uma última observação antes de seguir no tema, ainda dentro da parte básica, é com relação a declaração dos ponteiros. Você observou que o operador * tem duplo significado: se utilizado na declaração da variável, ele nos diz que a variável é um ponteiro, e; se utilizado no escopo do programa, em uma variável de ponteiro, ele nos diz que queremos desreferenciar o ponteiro, ou seja, utilizar o valor para o qual ele aponta, ao invés do seu próprio valor (que é um endereço).

Agora, pensando nisso, como fazemos para declarar vários ponteiros em uma única linha? Num primeiro momento, pensando em variáveis normais, declaramos assim:

int a, b, c;

Então, por conseqüência, declarar ponteiros deve ser igual, certo?

int* p, q, r;

Errado! Na verdade, o operador * faz parte do nome da variável, e não do tipo. Embora seja correto escrever tanto int* p como int *p (mudou apenas a posição do espaço), o * é considerado parte do nome e, portanto, a declaração acima irá produzir um ponteiro para um inteiro "p" e duas variáveis inteiras "q" e "r". A declaração correta é:

int *p, *q, *r;

Uma forma de demonstrar isto seria com o seguinte código:

int main() {
  char* c, d, e;

  cout << "sizeof: c = " << sizeof(c) << " d = " << sizeof(d) << " e = " << sizeof(e) << endl;

  return 0;
}

Saída:

sizeof: c = 4 d = 1 e = 1

Escolhi um tipo char desta vez por que com tipos inteiros não veríamos diferenças no sizeof. Uma maneira de corrigir este problema, além de declarar todos os ponteiros com o devido operador *, seria utilizar um typedef:

typedef char* CharPtr;

int main() {
  CharPtr c, d, e;

  cout << "sizeof: c = " << sizeof(c) << " d = " << sizeof(d) << " e = " << sizeof(e) << endl;

  return 0;
}

Saída:

sizeof: c = 4 d = 4 e = 4

Logo, o typedef garante que todas as variáveis sejam do mesmo tipo.

Equivalência com Arrays

Antes de entrar mais a fundo nas operações com ponteiros, existe uma última questão de sintaxe que vale a pena ser mencionada: a equivalência com arrays. Um array em C/C++ é declarado da seguinte forma:

int a[N];

Onde "a" é o nome do array e "N" é o seu tamanho.

Uma das propriedades do array é que, se utilizarmos apenas o seu nome, sem um índice referenciado, estamos tratando do endereço do primeiro elemento do array. Veja:

int main()
{
  int  a[10] = {0,1,2,3,4,5,6,7,8,9}; // Declaração de uma variável tipo array de int com tamanho 10

  cout << " a[0] = " << a[0] << "\t\t &a[0] = " << &a[0] << endl;
  cout << " a    = " << a    << "\t &a    = " << &a    << endl;
  cout << endl;

  return 0;
}

Saída:

 a[0] = 0                &a[0] = 0x28fed8
 a    = 0x28fed8         &a    = 0x28fed8


Na primeira linha pedimos para imprimir o valor do primeiro elemento (a[0]) e o endereço do primeiro elemento (&a[0]). Na linha seguinte, utilizamos duas notações equivalentes: tanto "a" como "&a" trazem o endereço do primeiro elemento.

Operações com Ponteiros

Espero que esteja tudo tranquilo até agora, pois agora vamos entrar na parte mais complexa. Lembra que o valor de um ponteiro é um endereço, certo? Então observe o seguinte:

int main() {
  int       *a = 0;
  char      *b = 0;
  long long *c = 0;
  float     *d = 0;
  double    *e = 0;

  cout << "sizeof:\n a = " << sizeof(a) << " *a = " << sizeof(*a) << endl
              << " b = " << sizeof(b) << " *b = " << sizeof(*b) << endl
              << " c = " << sizeof(c) << " *c = " << sizeof(*c) << endl
              << " d = " << sizeof(d) << " *d = " << sizeof(*d) << endl
              << " e = " << sizeof(e) << " *e = " << sizeof(*e) << endl;

  return 0;
}

Saída:

sizeof:
 a = 4 *a = 4
 b = 4 *b = 1
 c = 4 *c = 8
 d = 4 *d = 4
 e = 4 *e = 8

Ou seja, embora os tipos de dados que estes ponteiros apontem são totalmente distintos, os tamanhos dos ponteiros são exatamente os mesmos: 4 bytes. Isto se deve ao fato de que eu estou compilando este programa em modo 32 bits (4 bytes * 8 bits = 32 bits) e, portanto, todos os endereços estão dentro deste espaço. Note que se estivéssemos compilando em 64 bits, cada ponteiro teria 8 bytes.

Além disso, tanto em 32 bits como 64 bits nós conseguimos endereçar toda a memória como um bloco contínuo, mas se voltarmos um pouco no tempo e pensarmos em programas de 16 bits, isso não é totalmente verdade. Os processadores x86 em modo 16 bits utilizam uma forma mais complexa de endereçamento, composta por segmentos e offsets que compõem um endereço efetivo de 24 bits. Não vou entrar no detalhe, mas fica aqui como uma curiosidade para quem quiser pesquisar um pouco mais.

Para começar a trabalhar com operações em ponteiros, vamos trabalhar com a associação com arrays, pois desta forma estaremos trabalhando dentro de um espaço válido de endereçamento. Estas operações são possíveis em qualquer ponteiro, mas lembre-se sempre que a responsabilidade destas operações é totalmente do programador, o compilador não tem como checar se os limites estão sendo respeitados ou não, e bugs bizarros podem acontecer. Usando o array sabemos que temos o controle dos endereços de memória dentro daquela faixa.

A primeira operação é a adição:

int main() {
  int a[10] = {0,1,2,3,4,5,6,7,8,9};

  int *p = a;

  cout << "p = " << p << " *p = " << *p << endl;

  p = p + 1;

  cout << "p = " << p << " *p = " << *p << endl;

  return 0;
}

Saída:

p = 0x28fed4 *p = 0
p = 0x28fed8 *p = 1

Note que somar 1 ao ponteiro não somou 1 ao endereço do ponteiro, mas sim 4. Por quê? Porque o compilador sabe que aquele ponteiro aponta para um inteiro que ocupa 4 bytes, e ao somar 1 você está pedindo para o compilador que quer o próximo elemento alinhado com aquele ponteiro.

Por exemplo, se fizessemos o mesmo com um tipo char:

int main() {
  char a[10] = {'0','1','2','3','4','5','6','7','8','9'};

  char *p = a;

  cout << "p = " << (void*) p << " *p = " << *p << endl;

  p = p + 1;

  cout << "p = " << (void*) p << " *p = " << *p << endl;

  return 0;
}

Saída:

p = 0x28fef2 *p = 0
p = 0x28fef3 *p = 1

A diferença entre os endereços é apenas 1 byte, ou o tamanho do char.

Que outras operações são válidas em ponteiros? Basicamente todas operações relacionadas a adição e subtração:

int main() {
  int a[10] = {0,1,2,3,4,5,6,7,8,9};

  int *p = a;

  cout << "p = " << p << " *p = " << *p << endl;

  p++;
  ++p;

  cout << "p = " << p << " *p = " << *p << endl;

  p--;
  p -= 1;
  p += 3;

  cout << "p = " << p << " *p = " << *p << endl;

  return 0;
}

Saída:

p = 0x28fed4 *p = 0
p = 0x28fedc *p = 2
p = 0x28fee0 *p = 3

Sempre observando que o tamanho da variável apontada pelo ponteiro tem efeito direto nos cálculos dos endereços.

Note que, o endereço de um elemento "N" no array será sempre:

e = e0 + N * sizeof(T)

Onde "e0" é o endereço inicial do array e "T" é o tipo de dado do array.

Seguindo esta lógica, podemos ver que o operador [] nada mais é do que um operador de endereçamento, que abstrai estes cálculos para o programador.

Para testar assertiva, vamos fazer o seguinte (cuidado!):

int main() {
  int *p = 0;

  cout << "&p[1] = " << &p[1] << endl;
  cout << "&p[2] = " << &p[2] << endl;
  cout << "&p[3] = " << &p[3] << endl;
  cout << "&p[4] = " << &p[4] << endl;

  return 0;
}

Saída:

&p[1] = 0x4
&p[2] = 0x8
&p[3] = 0xc
&p[4] = 0x10

Note que mesmo declarando a variável "p" como um ponteiro (e não um array), podemos utilizar normalmente o operador []. O importante aqui é ressaltar que embora o programa tenha calculado os endereços corretamente, qualquer tentativa de acessar este espaço de memória pode ser desastrosa (por isso estou apenas imprimindo os endereços, não faço nenhum acesso a valor), logo, todo cuidado é pouco!

Conclusões

O meu objetivo com este artigo era fazer um panorama geral de operações com ponteiros. Eu acredito que uma vez que fique claro que trabalhando com ponteiros estamos trabalhando com endereços de memória grande parte da "mística" que envolve os ponteiros se desfaz e eles deixam de ser instrumentos obscuros para se tornarem ferramentas poderosas para o programador.

Como nota mental, fico devendo alguns tópicos interessantes para uma próxima edição deste artigo: chamadas de funções com ponteiros, arrays de arrays e ponteiros de funções.

segunda-feira, 18 de novembro de 2013

A máquina do tempo (para a internet)

A inspiração de escrever este post veio do seguinte video:


Basicamente, é uma apresentação de 10 minutos sobre como obter informações históricas da internet como tendências, métricas e etc, a partir de duas tecnologias: o HTTP Archive e o Big Query.

Isso me lembrou um "recurso" da internet que pouca gente conhece, e que por sinal é o irmão mais velho do HTTP Archive: o Internet Archive, mais especificamente a Wayback Machine. Enquanto o HTTP Archive tem o objetivo de armazenar, basicamente, metadados sobre as páginas da internet, a Wayback Machine armazena páginas da web completas. Ou seja, é possível, através de uma consulta a Wayback Machine, visualizar uma página hoje como ela era há anos atrás.

A Wayback Machine existe desde 1996 e têm algumas páginas realmente antigas. Por exemplo, eu consegui achar minha lista de item da minha antiga coleção de video games (que infelizmente não existe mais) com um snapshot de 2004:


Porém, se eu for mais para trás, consigo achar versões dela até 1999! Além disso, domínios mortos também podem ser vistos... o próprio SWI onde eu hospedava esta listagem não existe mais. Alguns sítios muito conhecidos e usados no passado como GeoCities e NBCI também podem ser acessados... é literalmente um museu em forma online, o que não poderia ser mais apropriado para a internet! :)

Agora, uma coisa que me deixa intrigado é, se existe uma forma de consolidar o Big Query com o HTTP Archive, será que não existiria uma forma de fazer o mesmo com o Internet Archive? Ou seja, pegar os dados históricos da Internet nos últimos 17 anos (1996-2013) e fazer análises (Hadoop?) para extrair dados históricos sobre a evolução da internet como um todo? Pelo que eu vi a facilidade de fazer isso com o Big Query se deve ao fato de que os dados do HTTP Archive já estão estruturados em bases MySQL (o Biq Query roda uma versão customizada do MySQL pelo Google), porém não encontrei uma forma de fazer o mesmo com a Wayback Machine, imagino que os dados não estejam realmente disponibilizados em nenhum formato similar, embora existam algumas APIs para consultas:


Enfim, fica aí um desafio para as próximas gerações... eu particularmente adoraria meter a mão na massa num projeto de pesquisas desse, mas infelizmente no momento tenho outras prioridades. Quem sabe um dia. De qualquer forma, fica aí a dica para quem quiser dar uma espiada no passado da web.