quinta-feira, 19 de novembro de 2015

Nada de novo no front

Sempre gostei de histórias onde pessoas contam seus casos de trincheira, casos onde soluções complexas e, algumas vezes, totalmente fora da caixa foram essenciais para que um problema fosse solucionado. Não somente casos envolvendo programadores, todos os campos do saber tem dessas histórias e são sempre interessantes! Se me recordo bem, mais ou menos a 9 meses atrás foi o último caso desse tipo que tive, antes de eu ser catapultado de forma abrupta da vida de programador. Infelizmente foi um lance bem kafkaesco mas, lamúrias à parte, vamos lá!

Existem vários níveis de pesadelo quando você está trabalhando com um sistema legado. Talvez o sistema não tenha nenhuma documentação, o que é péssimo, mas se o código for bem escrito você estará bem. Mesmo se for mal escrito,  o código está ai! use o método de dividir em partes pequenas e ir entendendo, e reescrevendo o que for necessário. Pode ser que você não esteja lidando diretamente com código legado, tem vez que o pesadelo vem na forma de um device fabricado na Galileia e com um manual em aramaico, justo quando o especialista em aramaico da equipe está ocupado com outras áreas do sistema.



Mas, mesmo o texto estando em um idioma que você não domina, usualmente o código estará escrito numa linguagem de programação que você conhece. Então você copia e cola o que tem no manual, contando com uma certa ajuda divina e vai ajustando até que o device faça o que tem que fazer de forma aceitável, é um processo terrível e que toma muito tempo, sem contar que o código resultante funciona sem ninguém saber ao certo o porquê, mas funciona. Outro caso acontece quando o um dos desenvolvedores é obrigado a trabalhar no natal e nomeou as variáveis de “porco_com_polenta, papai_noel, renas” (CASO REAL) e o sistema é uma mistura profana de COM+ e loucura (Redundante quando se trata de COM+), mas com ferramentas atuais de edição de código e alguma paciência, só o COM+ será um problema.

Só que algumas vezes o universo conspira contra você, no meu caso o problema veio na forma de uma biblioteca que gerava erros, às vezes eles eram esporádicos e causavam a quebra do sistema, outras vezes eles eram aparentemente inofensivos, mas sempre que o código executava em um debugger, as ferramentas piscavam como um arvore de natal doidona de LSD. E o maior problema dessa biblioteca é que o código fonte foi perdido! Perdido? Sim, perdido! Até onde fui informado, o código estava em um disquete que estragou e tudo o que restou foi uma versão já compilada no repositório.

Para melhorar, a empresa onde eu estava trabalhando tinha regras sobre instalação e utilização de software com licença de avaliação, sem contar com diversos NDA’s dizendo que eles vão sequestrar seu cachorro se qualquer linha de código do sistema vazar para qualquer mídia que seja. Então, mesmo que você tenha pensado anteriormente: “Fácil, baixo o IdaPRO, pego o que ele gera, já organizado, e posto uma dúvida no stackoverflow”. Não, caro desconhecido, você não terá essa opção. Esse sistema vinha sendo portado entre diversas plataformas (OS/2, Cygwin, Diversas distribuições e versões diferentes de kernel do linux) e, nessa dança macabra, o fonte se perdeu. O modulo era muito importante, mas por questões de tempo nunca houve apoio gerencial para parar a equipe e reescrever tudo. O erro que ele causava não era uma preocupação imediata, então os anos foram passando e ficou por isso mesmo.

Um dos erros nesse modulo era gerar um erro de read ao tentar acessar memória um byte além do tamanho do buffer passado como parâmetro, o clássico off-by-one. Por sorte, o modulo utilizava N chars, embora tentasse acessar o N+1, por esse acaso o resultado do calculo que ele realizava retornava correto. Por preguiça, todos os pontos do sistema que utilizavam essa biblioteca sempre alocavam um byte a mais do que o necessário, evitando que o debugger reclamasse. 

Mas, IMHO, independente de o erro ser algo sério e que acontece sempre ou só uma anotação na lista de bugs do debugger que aparentemente nunca causou nenhum problema, você não deve ser barato e se contentar com código mal escrito. Tem erro? conserte! Mas, como? Bem, vamos analisar o mesmo problema usando o seguinte programa exemplo (Neste caso o código falha praticamente sempre, pois quero um erro que ninguém diria: “ah, mas o resultado volta correto, deixa como está!”):

#include <stdio.h>
#include <stdlib.h>
#include "broken_lib.h"

int main()
{
  int value;
  int * data;

  data = (int *) malloc(sizeof(int) * 10);
  data[0] = 1;
  data[1] = 2;
  data[2] = 3;
  data[3] = 4;
  data[4] = 5;
  data[5] = 6;
  data[6] = 7;
  data[7] = 8;
  data[8] = 9;
  data[9] = 0;

  value = compute_some_stuff(data, 10);

  printf("[%d]\n",value);

  free(data);
  return 0;
}

#############################################################################

O header broken_lib.h:

$ cat broken_lib.h
#pragma once

 /* essa função deve somar todos os valores do buffer ‘input_data’ que tem tamanho ‘data_len’ e retornar o resultado, se algum dos valores do buffer for impar, antes de somar ela zera o bit 0, tornando este valor pár */
int compute_some_stuff(int * input_data, int data_len);

#############################################################################

O fonte correspondente ‘broken_lib.c’, como já foi informado, está perdido no limbo. tudo o que temos é uma lib estática:

$ ls *.a
libbroken.a

Quando executado, essa é a saída do programa:

$ ./main_prog 
[135160]

Ops… Levando em conta a documentação da função, deveriamos esperar o valor 40:

BUFFER ORIGINAL EM DECIMAL: 1+2+3+4+5+6+7+8+9+0
EM BINÁRIO: 0001 + 0010 + 0011 + 0100 + 0101 + 0110 + 0111 + 1000 + 1001 + 0000
REMOVENDO O BIT 0: 0000 + 0010 + 0010 + 0100 + 0100 + 0110 + 0110 + 1000 + 1000 + 0000
BUFFER FINAL EM DECIMAL: 0+2+2+4+4+6+6+8+8+0 = 40

Algo está muito errado! Vamos rodar no valgrind para ver se ele nos fornece alguma luz sobre o problema:

$ valgrind ./main_prog
==5035== Memcheck, a memory error detector
==5035== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5035== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==5035== Command: ./main_prog
==5035== 
==5035== Invalid read of size 4
==5035==    at 0x4006A6: compute_some_stuff (in /home/killocan/tuthack1/main_prog)
==5035==    by 0x400650: main (in /home/killocan/tuthack1/main_prog)
==5035==  Address 0x51fc068 is 0 bytes after a block of size 40 alloc'd
==5035==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5035==    by 0x4005B3: main (in /home/killocan/tuthack1/main_prog)
==5035== 
[40]
==5035== 
==5035== HEAP SUMMARY:
==5035==     in use at exit: 0 bytes in 0 blocks
==5035==   total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==5035== 
==5035== All heap blocks were freed -- no leaks are possible
==5035== 
==5035== For counts of detected and suppressed errors, rerun with: -v
==5035== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Vale notar que a saída (em azul) é correta quando o valgrind está rodando, isso se deve à forma como ele gerencia a memória do programa sendo instrumentado. Em vermelho notamos que o valgrind indica que houve um read inválido de 4 bytes. Ele informa também que esse acesso foi em uma região de memória depois de um bloco de 40 bytes alocado pela malloc (na minha máquina um int tem tamanho 4 bytes por isso o bloco alocado tem tamanho 40). Ele nos diz, também, que essa chamada à malloc foi realizada na função main. Com essa informação é seguro assumir que a função tentou acessar um indice N+1 no buffer passado pela main, o buffer apontado por ‘int * data’.



Ainda em vermelho, o valgrind nos diz o offset dentro do processo onde ocorreu esse read inválido. O código ofensivo está dentro da imagem do processo pois, no nosso caso fictício, a lib foi compilada estática no programa (i.e: todo código é copiado direto para o executável resultante).

Temos que ver o código da função ‘compute_some_stuff’ de alguma forma, no caso a ferramenta objdump era a minha única alternativa. O objdump, como diz o nome, exibe diversas informações sobre um ou mais arquivos objeto. Uma dessas funções é exibir o código assembly do código contido no arquivo. Nota para os que não sabem nada de assembly: Você deve aprender! É indispensável saber ler o asm da plataforma onde você desenvolve, é sua obrigação =D Isso vale para a turma das linguagens baseadas em VM, se você nunca olhou o bytecode gerado você está falhando como programador!



Saber assembly permite entender melhor como seu programa realmente funciona na máquina, além de permitir resolver problemas que seriam impossíveis caso você não saiba. Primeiro vamos ver quais funções existem na lib e quais estão sendo exportadas.

$ objdump -t libbroken.a 
In archive libbroken.a:

broken_lib.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS* 0000000000000000 broken_lib.c
0000000000000000 l    d  .text 0000000000000000 .text
0000000000000000 l    d  .data 0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame 0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g    F .text 0000000000000041 compute_some_stuff

Ótimo, a unica função (F) dentro da lib é a ‘compute_some_stuff’, e ela é global (g) isso vai tornar a saída em assembly mais fácil de ler.

$ objdump -d libbroken.a
In archive libbroken.a:

broken_lib.o:     file format elf64-x86-64
Disassembly of section .text:

 0000000000000000 :
   0: 55                    push   %rbp
   1: 48 89 e5              mov    %rsp,%rbp
   4: 48 89 7d e8           mov    %rdi,-0x18(%rbp)
   8: 89 75 e4              mov    %esi,-0x1c(%rbp)
   b: c7 45 f8 00 00 00 00  movl   $0x0,-0x8(%rbp)
  12: c7 45 fc 00 00 00 00  movl   $0x0,-0x4(%rbp)
  19: eb 19                 jmp    34 
  1b: 8b 45 f8              mov    -0x8(%rbp),%eax
  1e: 48 98                 cltq   
  20: 48 c1 e0 02           shl    $0x2,%rax
  24: 48 03 45 e8           add    -0x18(%rbp),%rax
  28: 8b 00                 mov    (%rax),%eax
  2a: 83 e0 fe              and    $0xfffffffe,%eax
  2d: 01 45 fc              add    %eax,-0x4(%rbp)
  30: 83 45 f8 01           addl   $0x1,-0x8(%rbp)
  34: 8b 45 f8              mov    -0x8(%rbp),%eax
  37: 3b 45 e4              cmp    -0x1c(%rbp),%eax
  3a: 7e df                 jle    1b 
  3c: 8b 45 fc              mov    -0x4(%rbp),%eax
  3f: c9                    leaveq 
  40: c3                    retq   

Antes de tentar entender como ela funciona é bom saber exatamente em qual instrução ela cometeu o read inválido, para saber essa informação devemos analisar qual instrução se encontra no offset 0x4006A6 do programa principal, que é onde o valgrind diz ter ocorrido o read:

$ objdump main_prog -d
[CODE]
…
4006a6: 8b 00                 mov    (%rax),%eax
…
[MORE CODE]

O que ele está fazendo é desreferenciando o que está em rax que certamente é um ponteiro para nosso buffer, e colocando o valor em eax. Essa mistura de um registrador de 64bits com a porção 32bits dele mesmo se deve ao fato de que ponteiros, na minha máquina, tem 8bytes só que ele aponta para um buffer de integers de 4 bytes, por isso a mistura de rax e eax. Durante um desses acessos o programa fez referencia para memória inválida. Vamos focar em entender a função nos atendo a como esse read aconteceu e não aos detalhes do funcionamento interno da mesma.

Vamos organizar o código por partes para ignorar o que não for relevante: 

Primeiro o setup do frame da função, nada que importe para nosso caso:
push   %rbp
mov    %rsp,%rbp

O código que segue pode parecer curioso para quem está acostumado com asm 32bits, pois ele está salvando em variáveis locais o valor dos registradores rdi e rsi:

mov    %rdi,-0x18(%rbp)
mov    %esi,-0x1c(%rbp)

Mas a razão é simples, eis um pedaço do código da função main quando vai chamar ‘compute_some_stuff’:

mov    -0x10(%rbp),%rax
mov    $0xa,%esi
mov    %rax,%rdi
callq  40067e

Ela passa os parâmetros nos registradores ao invés de colocar na pilha. Isso se deve a uma diferença importante entre o i386 system V ABI e o amd64 system V ABI. Estou usando uma maquina 64 com um kernel 64. Na i386 System V ABI os argumentos para uma função são passados na pilha e somente pela pilha. Já no amd64 System V ABI os argumentos são passados primeiro nos registradores (rdi, rsi, rdx, rcx, r8 e r9 se o argumento for inteiro e nos registradores xmm0..xmm7 se for um float). Somente quando esses registradores foram todos utilizados é que a pilha é usada.

A seguir a função zera duas variáveis locais:
movl   $0x0,-0x8(%rbp)
movl   $0x0,-0x4(%rbp)

Daí, então, a função salta imediatamente para o offset 0x34:
jmp    34 

No offset 0x34 encontramos:

34: mov    -0x8(%rbp),%eax
37: cmp    -0x1c(%rbp),%eax
3a: jle    1b 

Até o momento, sabemos que a função zera duas variáveis locais e salta para o offset relativo 0x34, lá ela move para eax uma dessas variáveis locais (variavel_1) e compara o valor do registrador com o parâmetro que foi passado para a função (vou assumir, baseado no código da função main ao fazer a chamada, que seja o parâmetro data_len, até porque não faria muito sentido comparar com o ponteiro do buffer). Se (variavel_1) for menor ou igual ao (data_len) a função retorna o controle para o offset 0x1b:

1b: mov    -0x8(%rbp),%eax

Então o código recupera novamente a variável local(variavel_1) e armazena em eax. Neste ponto já podemos ter uma visão da organização das variáveis locais e dos parâmetros da função:


É fácil notar que estamos olhando para um loop. Passando o olho no código vamos notar que essa variável é sempre incrementada em 1, antes de um salto condicional (jle):

30: addl   $0x1,-0x8(%rbp)

Vamos organizar as idéias. O código salta direto para um teste, caso esse teste seja avaliado como verdadeiro ela salta de volta e executa algum código, sempre incrementando variavel_1 em uma unidade, é válido assumir que estamos olhando para um laço for:

int variavel_1;
for (variavel_1 = 0; variavel_1 <= data_len; variavel_1++)

BINGO! Não precisamos entender mais nada do funcionamento interno da função. Basta lembrar que nós passamos como parâmetro o tamanho do nosso buffer, se o loop é controlado por uma condição <=, ele sempre vai acessar um item a mais do que deveria. Por sinal, sabemos que ele usa <= pelo uso da instrução “jle”, que significa “jump if less or equal”. Qualquer coisa, além do google, claro, olhe os manuais da intel, são ótimos.

E agora? Agora vamos realizar um patch binário nessa lib e mudar a instrução de jle para jl(jump less) que terá o efeito de mudar o teste para: variavel_1 < data_len. Isso vai corrigir o problema do loop, claro que podem existir outros erros na função, mas vamos corrigir o mais óbvio primeiro. Uma olhada no manual da intel de instruções (ou qualquer site ou livro que lhe apetecer) nos mostra que a instrução jle, para saltos curtos, usa o opcode 0x7E, já a instrução jl usa o opcode 0x7C. Então basta substituir o byte correto na lib, compilar novamente o programa e testar.

Para realizar esse trabalho existem diversas maneiras, uma delas é usar um editor hexadecimal, na época optei por escrever um programinha em C que abre a lib e atualiza o byte. Primeiro ache em qual offset esse byte está dentro da lib, uma ideia é usar a ferramenta hexdump em conjunto com o grep para filtrar a saida.

$ hexdump libbroken.a | grep 7e
00000e0 8948 e87d 7589 c7e4 f845 0000 0000 45c7
0000110 458b 3bf8 e445 df7e 458b c9fc 00c3 4347

Vale lembrar que a ferramenta hexdump, na configuração default, agrupa os bytes em words de 16bits e como estou usando uma máquina little endian a ordem dos bytes estarão “trocados” na visualização, então o par destacado em negrito estará, na verdade, ordenado como “7e df” no arquivo. Essa sequencia lembra algo?

3a: 7e df                 jle    1b 

Exato! Achamos o nosso jump. E o opcode da instrução está no offset 0x110 + 6. Fiz o seguinte programa:

patch.c:
int main()
{
  FILE * fp = fopen("libbroken.a", "rb+");
  fseek(fp, 0x116, SEEK_SET);
  fputc(0x7C, fp);
  fclose(fp);
  
  return 0;
}

Vamos testar?

$ gcc -o patch patch.c
$ ./patch
$ gcc -o main_prog main_prog.c -L. -lbroken
$ ./main_prog 
[40]

Ho Ho Ho! Agora a função retorna o valor que deveria. Vamos ver o código dela em assembly:

$ objdump -d libbroken.a
In archive libbroken.a:
[CODE]
…
37: 3b 45 e4              cmp    -0x1c(%rbp),%eax
3a: 7c df                 jl     1b 
3c: 8b 45 fc              mov    -0x4(%rbp),%eax
…
[MODE CODE]

Nosso patch funcionou perfeitamente e agora a função retorna o valor correto. Mas, será que passa no valgrind? Vejamos:

$ valgrind ./main_prog
==4503== Memcheck, a memory error detector
==4503== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==4503== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==4503== Command: ./main_prog
==4503== 
[40]
==4503== 
==4503== HEAP SUMMARY:
==4503==     in use at exit: 0 bytes in 0 blocks
==4503==   total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==4503== 
==4503== All heap blocks were freed -- no leaks are possible
==4503== 
==4503== For counts of detected and suppressed errors, rerun with: -v
==4503== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)



Agora nossa lib se comporta corretamente, retornando o valor esperado e sem gerar nenhuma operação de READ inválida. Muito bem, basta levantar da cadeira do trabalho e ir pra casa programar!!! That's all folks!

domingo, 28 de junho de 2015

sexta-feira, 12 de dezembro de 2014

Tutorial antigo

PDF de um tutorial bem antigo que eu tinha escrito para usar numa palestra sobre programação de jogos. Não é grande coisa, mas acredito que alguém iniciante pode tirar algo daí.

Videos Megaman - WIP

Alguns vídeos do jogo enquanto estou fazendo só para acompanhar andamento e evolução. Trata-se de um clone, o mais fiel possível(meu possível inclui minha burrice e também preguiça =D) do Megaman 1 do NES. Feito em C++ e Allegro, testo no windows 7 e no linux (ubuntu), mas deve rodar em qualquer lugar que suporte a lib Allegro.











Fonte dos testes.

Código fonte dos testes, enquanto escrevo os tutoriais :D A maioria compila com: g++ -o nome.exe nome.cpp -lalleg Se estiver usando VC++ daí só mandar compilar usando a lib da allegro no linker.

FILE 1
FILE 2
FILE 3
FILE 4
FILE 5
FILE 6
FILE 7
FILE 8
FILE 9
FILE 10
FILE 11
FILE 12

quarta-feira, 27 de novembro de 2013

Cmd - Autorun reg key.

HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\Autorun Executa o comando em Autorun sempre que o cmd sobe. A menos que /d seja passado como parametro para o cmd.

quinta-feira, 15 de setembro de 2011

Um jeito diferente de indexar arrays - não façam isso em casa!

O que você diria se encontrasse a seguinte linha de código no meio de um grande sistema que vc está trabalhando:

3[array] = x;

Alguns vão pensar que se trata de um erro de digitação bem estranho e que o modulo não está compilando, ai vão checar o Makefile, depois vão checar se não existe algum cache de código ativo, depois vão checar o repositório e começar a culpar o CVS, os defensores do GIT vão mostrar como CVS é uma coisa capenga... todos vão checar forums e depois de uma flamewar saudável vão começar a culpar o compilador, o SO, os ETs e vão acabar consultando um padre novo e um padre velho... A questão é que esta linha é válida e funcional! A sintaxe usada para indexar arrays em C é uma "maquiagem" que esconde a aritmética de ponteiros que acontece. A nível de código assembly, ponteiros e inteiros são praticamente a mesma coisa. Sendo assim a[b] diz algo como: some a com b, o resultado deve ser visto como um endereço de memória. Se a expressão estiver do lado esquerdo então é para escrever neste local de memória, se estiver do lado direito deve ler desse endereço, ou seja, deve desreferenciar o resultado como se fosse um ponteiro. Essa expressão será vista como, *(a + b) ou *(b + a). Como a adição é comutativa, a + b == b + a, daí a[b] == b[a]. A unica restrição sintatica nesse caso é: a ou b deve ser um ponteiro e o outro um inteiro. Isso funciona mesmo se um dos operandos é uma constante inteira, como o 3[array]. É este mesmo mecanismo que nos permite usar índices negativos em um array sem nenhum problema, veja:

int main()
{
int * p;
int a[10];
int i,j;

a[4]=42;
a[5]=4;

p = &a[5];

i = *p;
j = p[-1];

printf("%d %d\n",i,j);

return 0;
}

Este código imprime "4 42". Note que p[-1] vai se transformar em *(p+(-1)) -> *(p-1). Lembre que *(p+3) não significa, necessáriamente, que o resultado é o byte de endereço P+3, o operando que não é ponteiro, no caso 3, vai ser escalado pelo tamanho do tipo que p aponta, então se p aponta para um int a conta, para criar o código asm, é (endereço(p) + (3*sizeof(int)). Só não tente isso no trabalho ;) Mas se for tentar, aproveite e use com arrays de mais de uma dimenção:

int main()
{
int i;
int a[2][2];
int x,y;
x = 0;
y = !x;

a[0][1]=42;

i = y[x[a]];

printf("%d\n",i);

return 0;
}

WhuHAUWhauwhauwhauw Se for fazer, faça direito, ahm?!?! Cya!!!

terça-feira, 13 de setembro de 2011

A instrução LEA do x86

Um tempo atrás um doido aqui do trampo resolveu "melhorar" um pedaço do código escrevendo ele todo em assembly inline dentro dos modulos em c++ =D para completar, usou métodos pouco convencionais para executar algumas operações :) Isso rendeu muito papo para almoço e ele ganhou fama de ser maluco, se bem q ele já tinha essa fama :) Uma dos pulos do gato que ele implementou foi usar a instrução "lea" do x86 para multiplicar valores. Normalmente o manual da intel diz q LEA significa "load effective address", um pouco distante do famoso "MUL" :) Mas, devido a uma particularidade do modo de endereçamento dos x86, lea pode ser usada para muitas coisas alem de simplesmente funcionar como uma instrução para lidar com ponteiros. O assembly do x86 foi criado para "suportar" linguagens de alto-nível como C e Pascal de forma pouco dolorosa para os compiladores e nessas linguagens arrays de inteiros e estruturas pequenas são muito comuns. Vamos ver pra que LEA é utilizada "normalmente" :) Suponha q temos uma estrutura que guarda um valor de cor (RGB ou HSV):

struct Color
{
int rgb;
int hsv;
};

Para recuperar o valor do campo HSV escreveríamos algo como:

int hsvValue = colors[i].hsv;

Aqui colors[] é um array do tipo Color. Supondo que o compilador coloque o endereço base do array em EBX, e que a variavel i esteja em EAX, e que (rgb,hsv) são valores de 32 bits de forma que hsv esteja no offset 4 da estrutura (base+4), então a tradução para assembly poderia tomar a seguinte forma:

INTEL: MOV EDX, [EBX + 8*EAX + 4]
AT&T: mov 4(%ebx,%eax,8), %edx

Isso vai colocar o valor de "hsv" no registrador EDX. O valor de escala 8 é usado pq cada variável do tipo Color tem 8bytes, ou seja, soma o endereço base do array ao valor de "i" multiplicado pelo tamanho da estrutura então adiciona 4 que é o offset de "hsv". Mas é se quissemos o endereço de "hsv"? Vamos analisar esta expressão usando o operador & para recuperar o endereço de hsv:

int * pColor = &color[i].hsv;

Neste caso não queremos o conteúdo contido no endereço de memória, queremos o endereço de memória em si. Neste caso vamos usar a instrução LEA:

LEA ESI, [EBX + 8*EAX + 4]

Pq usar LEA e não MOV? Pq neste caso se você trocar por MOV o que vai acontecer é: o cpu vai efetuar o calculo EBX+8*EAX+4 e vai encontrar um valor, este valor será encarado como um endereço de memória então o cpu vai neste endereço buscar o conteúdo que está armazenado lá. O que claramente não é nossa intenção. Podemos dizer que LEA é uma instrução que executa cálculos envolvendo memória, mas sem acessar estes endereços. De certa forma, é mais uma operação de soma e shift só que com um nome estranho :) Até daria para utilizar MOV, veja esse programinha em C:

#include
int main()
{
int * p;
int a = 2;
p = &a;
printf("%d\n",*p);
return 0;
}

Se olharmos o código assembly gerado pelo compilador(no meu caso o gcc) o código será parecido com:

main:
// Setup da pilha
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp

movl $2, 24(%esp) // Move 2 para a região da pilha onde está o 'a'
leal 24(%esp), %eax // Carrega eax com o endereço de 'a'
movl %eax, 28(%esp) // Move para onde fica 'p'
movl 28(%esp), %eax // Move o endereço de 'a'(valor de 'p') para eax
movl (%eax), %edx // Carrega edx com o valor de 'a'

// Setup para chamar printf
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret

Obvio que o código não é otimizado, mas veja que ele usa lea para carregar o endereço de 'a' em 'p', poderíamos ter chegado ao mesmo resultado usando MOV, da seguinte forma:

main:
// Setup da pilha
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp

movl $2, 24(%esp) // Move 2 para a região da pilha onde está o 'a'

// Faz o que o LEA faz usando um MOV e um ADD
movl %esp, %eax
addl $24, %eax

// O resto é igual! :)
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl (%eax), %edx
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret

Dá para notar que usamos 2 instruções ao invés de uma, não muito bom :) E esse é o principal sentido de usar LEA, poupar cálculos e resolver o endereço de memória que queremos e depois usar, num loop, por exemplo, onde seria custoso ficar calculando offsets o tempo todo. Se você mudar o LEA para MOV no primeiro caso provavelmente vai quebrar o programa, pq MOV vai tentar acessar o endereço calculado para recuperar o valor. Um outro exemplo é como recuperar o valor de um ponteiro, vejamos, dado o seguinte programinha em C:

#include
int main()
{
int * p;
int a = 2;
p = &a;
printf("%d\n",a);
return 0;
}

Esse programa mostra 2 na saída padrão. O código assembly gerado pelo compilador(Gcc) será parecido com:

main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $2, 24(%esp)
leal 24(%esp), %eax
movl %eax, 28(%esp)
movl 24(%esp), %edx
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
movl $0, %eax
leave
ret

Vamos supor que alguem apagou nosso código em C e vc, por alguma razão maluca qualquer, tem que trocar a linha printf("%d\n",a); para printf("%d\n",*p); fariamos o seguinte:

Já carregamos o endereço de 'a' em 'p' com a instrução movl %eax, 28(%esp). Podemos notar que antes de colocar o valor dos parâmetros do printf na pilha o compilador moveu 'a' para edx e o endereço da string para eax. O que vamos fazer é trocar a instrução movl 24(%esp), %edx por:

movl 28(%esp), %edx
movl (%edx), %edx

Já que 'p' está no endereço (esp+28) nós temos que reaver o que ele contem (o endereço de 'a') e fazemos isso com o movl 28(%esp), %edx ai temos que acessar esse ponteiro movl (%edx), %edx e armazenar em edx o valor contido no endereço que estava armazenado em 'p' (sendo q 'p' contem o endereço de 'a'). Agora que já vimos bastante o LEA, vamos traduzir algumas utilizações do LEA para português:

leal (%ebx), %eax

Esta instrução copia o endereço da expressão (%ebx) para eax. Como o "endereço efetivo" é o valor de ebx, essa instrução vai copiar ebx em eax como se fosse um MOV. Nada muito útil :)

leal 3(%ebx), %ebx

Copia o valor de ebx + 3 para ebx. Não muito útil já que um ADD faria o mesmo, possivelmente mais rápido.

leal 3(%ebx), %eax

Aqui temos uma utilização mais sensata de LEA, ela vai copiar o valor de ebx somado a 3 em eax. Isso significa que fizemos um MOV + ADD em uma única instrução! Ok, e o que LEA tem com MUL!?!? Se usarmos a capacidade de LEA operar cálculos com endereços sem acessar a memória podemos usar isso para multiplicar por 2,4,8 ou até por 3,5,9, ou até executar essa multiplicação e somar um inteiro. Usando LEA para multiplicar:

leal (%eax,%eax,2), %eax // EAX = EAX * 2 + EAX = EAX*3
leal (%eax,%eax,4), %eax // EAX = EAX * 4 + EAX = EAX*5
leal (%eax,%eax,8), %eax // EAX = EAX * 8 + EAX = EAX*9

Outros exemplo:

leal 8(,%eax,4), %eax // Multiplica eax por 4 e adiciona 8

No Asm AT&T a sintaxe displacement(base register, offset register, scalar multiplier) é equivalente ao [base register + displacement + offset register * scalar multiplier] na sintaxe da Intel. Um ou ambos os parâmetros numéricos e um dos registradores podem ser omitidos. Sendo que o fator de escala deve ser 1,2,4 ou 8. Ok! Viram! Com LEA vc pode realizar alguns calculos (e sem modificar os flags! isso consta na documentação da Intel). Se você consultar qualquer guia de otimização de código (principalmente para guiar compiladores) verá que LEA pode fornecer métodos bem úteis de realizar operações matemáticas! É isso pessoal! Meu parecer é esse tal gato que meu brother fez não é tanto um gato, é muito mais uma técnica muito boa!!!!

quinta-feira, 3 de fevereiro de 2011

'\n' ou std::endl - Qual newline usar? (C++)

Seja por economia de espaço ou por pura preguiçite aguda é comum que uma boa parte dos programadores escolha usar '\n' para quebrar um linha, principalmente para logar informações de debug no console. No entanto, qual é a diferença? Bem a diferença principal é que o std::endl faz um flush no buffer (para streams que suportam buffer) então se você não quer que o buffer receba um flush com muita frequencia é melhor usar o \n, mas se você quer evitar o flush automatico do buffer e deseja ter um controle melhor de quando o buffer será comitado use std::endl.

De um ponto de vista simplista:

std::cout << std::endl;

// é equivalente a

std::cout << "\n" << std::flush;

Claro que quem estiver usando o operador '<<' deve saber que operações de I/O, mesmo bufferizadas, são operações lentas. Então não leve o assunto performace TÃO a sério nesse cenário. Em contra partida lembre que o flush pode criar algumas chamadas a mais e se o seu código é uma classe de log em arquivo no disco, por exemplo, então você pode ter uma perda de performace SIM. Vale lembrar que isso acontece pois, no geral, escrever um bloco de bytes é mais economico do que escrever varios bytes um de cada vez, os dispositivos são desenhados para efetuar operações com multiplos bytes então aproveite essa feature! E lembre-se que no geral, para arquivos abertos em modo texto, o newline será mapeado para o caracter correto de fim de linha(LF em Unix, CRLF no DOS(Windows), CR no Macintosh, e por ai vai), vale lembrar que cout e cin são arquivos especiais, ou uma especie de arquivo(se você prefere assim) e também vão se comportar como tal. Para não manter o artigo tão superficial vamos olhar a implementação do endl na libstdc++ da GNU:

/**
*  @brief  Write a newline and flush the stream.
*
*  This manipulator is often mistakenly used when a simple newline is
*  desired, leading to poor buffering performance.  See
*  http://gcc.gnu.org/onlinedocs/libstdc++/27_io/howto.html#2 for more
*  on this subject.
*/

template
basic_ostream<_CharT, _Traits>& endl(basic_ostream<_CharT, _Traits>& __os)
    {
    return flush(__os.put(__os.widen('\n')));
    }

Veja que a documentação é bem clara quanto ao comportamento do endl, mas olhando o código fica claro o que acontece. O endl recebe o stream que vai operar como parâmetro e chama o put para inserir um '\n'. O retorno do put será enviado para o flush causando, obviamente, um flush na stream.

That's all!!!

terça-feira, 2 de março de 2010

Ponteiros para funções – C

Ponteiros são os grandes bichos-papões de quem está querendo aprender C, e ponteiro para função é o über monstro para a maioria dos programadores novatos. Mas, basta um pouco de paciência e um olhar destemido para perceber que não existe nada de tão assustador sobre eles. Assumindo que você conhece ponteiros vou me concentrar no tópico ponteiro para funções. É importante saber distinguir uma declaração de função de um ponteiro para função. Nos exemplos abaixo:

- int * func();
- int (*func)();

O primeiro caso define uma função que retorna um ponteiro para um inteiro, já o segundo caso é um ponteiro para uma função que retorna int. Lembre-se ( ) tem precedência ao * logo o primeiro caso será avaliado como uma função e o segundo como um ponteiro (*func) para uma função int (). Ponteiros para função não são somente um jeito complicado de programar, são extremamente úteis, pois podem substituir longas cadeias de if/else ou um switch gigante, são úteis para criar callbacks, e, de certa forma, sem eles não existiria como implementar mecanismos de ligação dinâmica de funções como DLL's já que você solicita um nome na tabela de símbolos do dll, e a partir de um ponteiro de função você consegue chamar a função cujo código foi carregado na memória possivelmente após a imagem do seu processo ter sido carregado. Um ponteiro para função funciona pois, no final das contas, o código das funções também estão na memória e se o controle da cpu for transferido para esse endereço o código será executado, claro que o endereço tem que ser um endereço aonde seu processo tem privilégios de execução. Para declarar um ponteiro para uma função utilizo mentalmente um jeito bem simples, e que funciona bem na maioria dos casos. Esse método consiste em escrever como se você estivesse declarando a função, por exemplo:

int f(int a);

Então coloque parênteses ao redor do nome da função e um asterisco na frente do nome:

int (*f)(int a);

Note que não é necessário colocar o nome da variável que é recebida como parâmetro, mas talvez seja uma boa para fim de entendimento do código. Ok, agora vamos continuar a brincadeira e pensar grande, quem sabe um ponteiro para uma função que receba um inteiro retorne um ponteiro para um float? Simples, primeiro escreva como se estivesse declarando:

float * f(int i);

Depois, faça a jogada que aprendemos anteriormente:

float * (*f)(int i);

Pronto! Viu como a coisa nem é tão complicada assim! Antes de demonstrar aspectos práticos vamos declarar mais uns ponteiros para funções que pareceriam intragáveis antes. Digamos, um ponteiro para uma função que retorna um array de ponteiros para funções que retornam um ponteiro void e não recebem parâmetros. Somando a isso, a função recebe como parâmetro dois inteiros e um ponteiro para int, hehehe bem até a declaração disso é meio bizarro, mas vamos lá:

void * (**f(int a, int b, int * pi))(void)

Ok, isso merece uma explicação hehehe, f é o nome da função, ela recebe dois inteiros e um ponteiro para inteiro, o retorno dela é um ponteiro para ponteiro **f e esse ponteiro é para um array de ponteiros para função do tipo void * (void), ou seja, que recebe void e retorna um ponteiro void. Como é mais comum retornar o endereço do primeiro item do array, então temos nossa função declarada. Criar um ponteiro, neste caso consiste em:

void * (**(*fp)(int a,int b,int* pi)) (void);

Lindo não?! Removendo o nome dos parâmetros fica ainda mais bonito:

void * (**(*fp)(int,int,int*)) (void);

Mas, acredite, é bem pouco provável que você tenha que criar uma loucura dessas, o propósito do meu exemplo é só para demonstrar que até as funções mais loucas podem ter um ponteiro apontando pra elas sem muito trabalho, pelo menos não é tão absurdo como dizem. Que tal um exemplo disso em funcionamento hehehe:

#include <stdio.h>

void * fvoid1(void)
{
fprintf(stderr,"fvoid1\n");
return NULL;
}

void * fvoid2(void)
{
fprintf(stderr,"fvoid2\n");
return NULL;
}

void * (*farray[]) () = {fvoid1,fvoid2};

void * (**f(int a, int b, int * pi))(void)
{
void *(**fp)() = &farray[0];
return fp;
}

int main()
{
void * (**(*fp)(int,int,int*)) (void);
void * (**a)(void);
fp = f;
a = (*fp)(10,20,NULL);

(*a)();

a++;

(*a)();

return 0;
}

Atribuir o endereço de uma função para um ponteiro de função segue o mesmo padrão do resto das coisas em C, digo, se você criar um ponteiro de função que aponte para uma função cuja definição combine com a definição da função printf, pro exemplo, atribuir o endereço é tão simples quanto:

pf = printf;

Ou você pode utilizar a forma mais “canônica” e utilizar o operador & para recuperar o endereço da função. Vale lembrar que alguns compiladores vão reclamar da forma direta, então já que o jeito certo é o canônico, utilize o jeito certo! Como em:

pf = &printf;

Para chamar, você tem duas opções, você pode utilizar a sintaxe:

pf(“Alo mundo”);

Desta forma o compilador vai desreferenciar o ponteiro para você, ou você pode fazer na mão:

(*pf)(“Alo mundo”);

Não tenho nenhum argumento contra ou a favor a utilização de um método ou do outro, no máximo eu diria que desreferenciar na mão deixa um pouco mais claro que você está utilizando um ponteiro e não chamando uma função chamada pf.
Agora vamos tratar os assuntos mais comuns relativos ao uso de ponteiros para função. Evitar uma cadeia de if/else gigante ou um switch e funções que são passadas para funções*, de certo modo, para implementar uma técnica conhecida como callback. Pretendo matar dois coelhos com uma cajadada só, e explicar ambos no mesmo exemplo. O problema com as cadeias de if/else ou switch, geralmente, surge quando é necessário fazer a chamada para uma função apropriada para algum caso especifico quando a situação não pode ser determinada em tempo de compilação, ou seja, não há como saber antes de executar qual função deve ser chamada. Vou utilizar o caso mais clássico que é o das quatro operações básicas (+/-/*/div), o código utilizando um switch seria parecido com:



#include <stdio.h>

float Plus (float a, float b) { return a+b; }
float Minus (float a, float b) { return a-b; }
float Multiply (float a, float b) { return a*b; }
float Divide (float a, float b) { return a/b; }

void Solve(float oper1, float oper2, char opCode)
{
float result=0.0f;

switch(opCode)
{
case '+': result = Plus (oper1, oper2);break;
case '-': result = Minus (oper1, oper2);break;
case '*': result = Multiply(oper1, oper2);break;
case '/': result = Divide (oper1, oper2);break;
}

printf("Resultado: %f %c %f = %f\n",oper1,opCode,oper2,result);
}

int main()
{
Solve(2,3,'+');
Solve(7,3,'-');
Solve(2,7,'*');
Solve(25,5,'/');

getchar();

return 0;
}


Utilizando uma função que utiliza ponteiros de função como parâmetro, ou seja, recebe uma função como parâmetro, ficaria parecido com:


#include <stdio.h>

float Plus (float a, float b) { return a+b; }
float Minus (float a, float b) { return a-b; }
float Multiply (float a, float b) { return a*b; }
float Divide (float a, float b) { return a/b; }

float Solve(float oper1, float oper2, float (*f)(float,float))
{
return ((*f)(oper1,oper2));
}

int main()
{
printf("Resultado: 2 + 3 = %f\n",Solve(2,3,Plus));
printf("Resultado: 7 - 3 = %f\n",Solve(7,3,Minus));
printf("Resultado: 2 * 7 = %f\n",Solve(2,7,Multiply));
printf("Resultado: 25/5 = %f\n",Solve(25,5,Divide));

getchar();

return 0;
}


E obvio que este caso é um caso trivial e não existe necessidade real de utilizar um ponteiro para função. A coisa muda de figura em casos aonde algum trabalho extra é pré-requisito para o funcionamento das operações, imagine receber os operandos via RPC ou que seja necessário converter a entrada do usuário em uma GUI para valores float a fim de obter os operandos, ou qualquer coisa do tipo. O código para executar esta tarefa não teria que ser replicado em todos os casos do switch bastaria codificar uma vez e baseado na necessidade chamar passando o ponteiro para a função correta. Essa técnica de passar uma função para outra é chamada de callback, e a função sendo chamada geralmente recebe o nome de callback function.
Normalmente callbacks são utilizadas quando é necessário que duas camadas diferentes de um sistema se comuniquem como, por exemplo, você passa uma função para o SO de forma que, quando um evento qualquer ocorrer o sistema chame essa função pra você.
Note que fiz um apontamento * quando falei sobre passar uma função para outra, no caso de C/C++ não é isso que ocorre de fato. Em linguagens imperativas como é o caso de C/C++, normalmente não permitem tal operação o que estamos fazendo é meramente passar o endereço de onde a função está na memória. Funções que recebem outra função como parâmetro, e de fato implementam funções de alta ordem gerais só são possíveis em linguagens funcionais e não vou tratar disso neste tutorial e possivelmente não neste blog heheheh. Então é isso, da próxima vez que pensar em ponteiros para função ao invés de medo, sorria, pois é um recurso muito poderoso da linguagem e que não é tão complicado quanto dizem por ai!

That's all folks!

terça-feira, 23 de fevereiro de 2010

Memset e suas pegadinhas

Em C a forma mais comum de iniciar um array, o que, normalmente, implica em atribuir o valor 0 a todos os índices de um array é usando a função memset da biblioteca default do C. Alguns programadores que gostam de reinventar a roda, como eu, podem acabar criando um loop para iterar nos itens do array e fazer a atribuição na mão, só que isso é lento. Embora seja a forma mais natural e, teoricamente, a que executa em melhor tempo, dentro das restrições da linguagem C pura, é um jeito forçado de implementar uma rotina como essa. É comum que as funções da biblioteca default sejam codificadas em assembly e além disso, pelo menos nos cpus da intel, existem instruções que fazem a copia de um valor diversas vezes na memória iniciando em um endereço base qualquer, e isso acontece muito mais rápido do que usando um loop em C. Em muitos casos cpus não gostam de loops, mas deixa isso para um outro post, por agora fixe sua mente no seguinte: Por mais que você se ache fodão, você não é melhor que a galera que codifica a biblioteca padrão, seja da GNU, da Microsoft, da Intel ou outra implementação de grande porte existente. Então usar memset é o jeito mais rápido e padronizado de realizar esta tarefa.

Só que nem sempre desejamos iniciar o array com o valor 0, e se você quiser que todos itens armazenem o valor 5? Aqui vem a pegadinha, alguém poderia surgir com a idéia: Simples, uso memset! Ok, pode funcionar, caso você esteja utilizando um array de char, que acaba sendo um byte na maioria dos casos. Mas se seu array for um array de inteiros, aí a coisa muda de figura. Vejamos o que o manpage do Linux tem a dizer sobre o memset(Tomei a liberdade de traduzir de forma que faça mais sentido em português):

NOME

memset - Preenche uma região de memória com um valor(byte) constante.

SINOPSE


#include < string.h >

void *memset(void *s, int c, size_t n);


DESCRIÇÃO

A função memset() preenche os primeiros n bytes da área de memória apontada por s com o valor constante do byte c.

Lembrete: Embora o parametro c seja um inteiro somente o primeiro byte, o de menor ordem, será utilizado e esse byte será convertido para um byte sem sinal, unsigned byte. Então mesmo que você tente passar o valor -1 ele será visto como 255 internamente à memset, logicamente o resultado real vai depender do tipo do container que você estiver utilizando.

Ok, e daí? Daí que a função memset opera copiando bytes e um inteiro (int) ocupa, normalmente, quatro bytes na memória. Então o que acontece quando você tenta copiar um valor diferente de zero para um array de inteiro é o seguinte:

Imagine que as caixinhas abaixo são os bytes na memória que correspondem a um inteiro (Estou assumindo um cpu little endian - se não sabe do que se trata espere um post futuro ou procure no google!) Imagine também que o valor zero está armazenado no inteiro, os números sobre as caixinhas indicam o endereço base começando em 0:

0[00000000]1[00000000]2[00000000]3[00000000]

Este é um int na memória com o valor 0, um inteiro com o valor 42 seria armazenado da seguinte forma, em binário:

0[00101010]1[00000000]2[00000000]3[00000000]

Quando você usa memset em um array de char, tudo bem, o código funciona pois, um char na maioria dos casos ocupa um byte na memória ou seja um único quadrinho:

0

[00000000]

Em um array de 5 elementos:

char cArray[5];

memset(cArray,5,sizeof(char)*5);

Tudo funciona como esperado:

endereço: 0

[00000101] indice: 0

endereço: 1

[00000101] indice: 1

endereço: 2

[00000101] indice: 2

endereço: 3

[00000101] indice: 3

endereço: 4

[00000101] indice: 4

Cada elemento contem o valor 5 como esperado, agora quando você tentar executar um código como:

int iArray[5];

memset(iArray,5,sizeof(int)*5);

Eis o que vai acontecer:

0[00000101]1[00000101]2[00000101]3[00000101] indice: 0

4[00000101]5[00000101]6[00000101]7[00000101] indice: 1

8[00000101]9[00000101]10[00000101]11[00000101] indice: 2

12[00000101]13[00000101]14[00000101]15[00000101] indice: 3

16[00000101]17[00000101]18[00000101]19[00000101] indice: 4

Cada índice vai conter o valor: "00000101000001010000010100000101" cuja representação em decimal é 84215045, um bocado longe do que era desejado. Para reforçar o assunto vamos utilizar outro valor e ver o que acontece. Imagine o valor 255 que é o maior valor que um byte sem sinal pode representar, lembrando que o byte, sendo composto por oito bits, pode representar 256 valores distintos, um intervalo de 0 a 255 normalmente. O valor 255 em hexa 0xFF pode ser armazenado com folga em um int, mesmo em um int com sinal. O valor 255 não pode ser representado em um char com sinal já que um signed char utiliza o primeiro bit para indicar sinal. Até aqui tudo bem, qualquer programador com meio neurônio sabe disso, mas seria razoável alguém cometer o erro do memset contando com o fato de que um int suporta um byte com sobra. A mesma operação usando o valor 255 teria como resultado o seguinte:

0[11111111]1[11111111]2[11111111]3[11111111] indice: 0

4[11111111]5[11111111]6[11111111]7[11111111] indice: 1

8[11111111]9[11111111]10[11111111]11[11111111] indice: 2

12[11111111]13[11111111]14[11111111]15[11111111] indice: 3

16[11111111]17[11111111]18[11111111]19[11111111] indice: 4

Cada índice contem o valor “11111111111111111111111111111111” que em decimal é “4294967295” ou em hexa 0xFFFFFFFF, agora se nosso array tiver sido declarado como int, o que por default faz dele um signed int, teremos o valor -1 armazenado em cada um dos 5 elementos do array, o que com certeza é uma surpresa nada agradável para um programador olhando um dump de memória ou tentando debugar o código, afinal se eu copiei o valor 255 em um inteiro como é que posso ter -1? E não para por ai, para arrays do tipo float a coisa é ainda pior, floats em C são codificados de acordo com o padrão IEEE 754, e o único valor que tem uma correspondência binária entre valores inteiros e valores de ponto flutuante é o valor/número zero, todos os outros diferem, e muito, em sua representação. Sendo assim o único uso de memset com floats será para atribuir o valor zero, qualquer outra tentativa resultará em um valor errado e dificílimo de ser entendido. Mesmo sabendo o padrão binário armazenado na região de memória é complicado definir o valor codificado como ponto flutuante no olhometro, ao contrário de valores codificados como inteiro. Por exemplo, o valor decimal 457 é armazenado como inteiro no seguinte padrão “111001001” se somar um, ele passa a valer, logicamente, 458 e a representação binária é “111001010”, ambos facilmente legíveis, basta uma operação mental ou canetal de troca de base para saber o valor. O maior problema que poderia ser encontrado seria eventuais diferenças entre maquinas big endian e little endian. Agora, usando armazenamento em variáveis de ponto flutuante o valor 457 é codificado em binário como “1000011111001001000000000000000” já o valor 458 é representado como “1000011111001010000000000000000” note que embora seja possível encontrar o valor aí, não é nada human-readable. Logo evite codificar valores de ponto flutuante na mão ;) É claro que você pode codificar na unha o valor desejado, mas não existe nenhuma situação razoável onde este truque faça sentido. Então, para finalizar, fique atento aos atalhos que algumas funções da biblioteca padrão, como a memset, parecem oferecer pois, você pode acabar mandando seu míssil teleguiado pro lugar errado devido a um erro de calculo astronômico causado por valores espúrios que podem resultar de um erro tão simples quanto esse. Abaixo um código que demonstra exatamente o comportamento que foi descrito no artigo:


#include < stdio.h >
#include < stdlib.h >
#include < string.h >

int main()
{
int iArray[5];
int i;

printf("Valores no array(lixo):\n");
for (i = 0; i < 5; ++i)
{
printf("%d ", iArray[i]);
}
printf("\n");

memset(iArray,0,sizeof(int)*5);

printf("Valores apos o memset(com 0): \n");
for (i = 0; i < 5; ++i)
{
printf("%d ", iArray[i]);
}
printf("\n");

memset(iArray,5,sizeof(int)*5);

printf("Valores apos o memset usando o valor 5: \n");

for (i = 0; i < 5 ++i)
{
printf("%d ", iArray[i]);
}
printf("\n");

return 0;
}


A primeira linha da saida depende do ambiente uma vez que depende do que estiver
na memória no momento da execução, porem, no geral, a saida será parecida com:


187-24-217-3:~ killocanmhc$ ./teste
Valores no array(lixo):
0 0 0 0 -1881139893
Valores apos o memset(com 0):
0 0 0 0 0
Valores apos o memset usando o valor 5:
84215045 84215045 84215045 84215045 84215045


That's all folks!

segunda-feira, 22 de fevereiro de 2010

new/delete new[]/delete[]

Não pretendo esgotar o assunto new/delete, pois não é esse o propósito do meu blog. Pretendo demonstrar somente a diferença entre a versão para array e a para objetos e mostrar como o código se comporta em cada caso, em um próximo post pretendo falar sobre sobrecarga dos operadores new/delete, sobre construtores para tipos primitivos, e sobre como atacar código que faz um mau uso do new/delete. Dito isso, e levando em conta uma plataforma Intel 32bits, vamos lá! É regra que os programadores novatos em C++ decoram a forma de utilizar delete e delete[] como se fosse um motto: “Usarei delete quando usar new e delete[] para new[]”. Também é fato que poucos sabem explicar o que motiva o design da linguagem a separar o significado de destruir um único objeto de destruir um array e, principalmente, como eles funcionam! Quanto à separação, tudo remota a uma questão histórica; antigamente, bem nos primórdios do c++, o operador delete para arrays utilizava um argumento que indicava quantos itens deveriam ser liberados, como no exemplo:

p = new AwesomeObj[iSize];

delete[iSize] p;

Não é necessário comentar o tanto que isso potencializa a tendência ao desastre. E vale lembrar que essa tendência de causar desastres é uma característica intrínseca do c++ hehehe. Exigir que o código mantenha uma contagem sobre quantos elementos foram alocados é uma tática muito perigosa e força o design do programa a carregar esse fardo, sem contar que, no caso de a memória poder ser liberada em funções de callback ou fora do escopo da thread corrente, informação adicional teria de ser passada de um lado para o outro criando um overhead, muitas vezes, inaceitável. Em revisões mais novas da linguagem o operador foi modificado não necessitando mais de um indicativo de quantos elementos devem ser destruídos, no entanto a distinção entre destruir um único objeto com delete e um array com delete[] foi mantida.

Para entender melhor o funcionamento de cada um, caso você não saiba exatamente o que tem feito nesses seus dias de programação hehehehe, é necessário saber um tiquinho como C/C++ organiza a memória, mas não se assuste só um tiquinho hehehe. Permita-me antes falar um pouco sobre um fato: C++, ao contrário de C, pode lidar com classes que possuem, pelo menos, dois métodos especiais, o construtor e o destrutor. Quando alocamos memória em C com malloc, por exemplo, tudo o que estamos fazendo é requisitar um pedaço de memória e nada alem disso. Já new além de alocar uma quantidade de memória que possa conter nosso objeto também chama o construtor do mesmo, e isso vale para delete, antes de liberar a memória, como a função free faz em C, o operador delete chama o destrutor do objeto. Esse comportamento vale também para a versão array, ou seja, new[]/delete[], em código isso se traduz em:

char* p1 = new char[100];

char* p2 = (char *) malloc(100);

// p1 e p2 apontam para 100 bytes

// (considerando um char de um byte) alocados na memória.

// nesse caso as duas chamadas são equivalentes em sentido.

delete [] p1;

free(p2);

// Ambos liberam os 100 bytes alocados,

// mais uma vez, em sentido, as chamadas são equivalentes.

Agora vem a diferença, considere uma classe qualquer como, por exemplo, uma chamada AwesomeObj:

class AwesomeObj

{

public:

AwesomeObj() { printf("Construtor\n"); }

~AwesomeObj() { printf("Destrutor\n"); }

};

AwesomeObj *t1 = new AwesomeObj[100];

AwesomeObj *t2 = (AwesomeObj*) malloc( sizeof(AwesomeObj) * 100 );

// Agora t1 e t2 ambos apontam para 100 AwesomeObj na memória.

// Entretanto a primeira chamada, com new[], vai alocar a memoria para os 100

// AwesomeObj e vai chamar o construtor de cada um deles, já a versão com malloc não!

delete [] t1;

free(t2);

// Aqui o mesmo, a versão com delete[] vai chamar o destrutor de cada um dos 100

// AwesomeObj antes de liberar a memória, já o free não vai.

Então vem a pergunta e se eu usar delete para algo alocado com new[] ou usar delete[] para algo alocado com new? A resposta é: depende hehehe, o subsistema de memória e a implementação do seu compilador que vai ditar o que de fato é feito para alocar/desalocar um objeto, mas em linhas gerais quando você usa new o código irá alocar memória de acordo com o tamanho do tipo que você requisitou e chamar o construtor, se houver, do objeto recém alocado. No caso de utilizar new[] ele vai chamar o construtor de todos os itens que forem alocados, alem de manter uma sentinela que indica quantos objetos foram alocados. A localização exata dessa sentinela varia de compilador para compilador e de plataforma para plataforma. Para alocar memória o operador new/new[] vai chamar alguma função intermediaria entre o SO e a biblioteca default, chamada libc no caso do gcc, digamos que malloc seja utilizada para realizar a tarefa de alocar a memória. A função malloc, dependendo da implementação, irá alocar os X bytes requisitados + quatro, sendo que esses quatro bytes adicionais correspondem a uma palavra(WORD) em maquinas 32bits, essa palavra vai ficar no endereço &p – sizeof(unsigned int), ou, em miúdos, quatro bytes antes do inicio da área que é retornada para ser utilizada. Utilizando este valor o free sabe quantos bytes deve liberar. Daí que o operador delete[] irá realizar um trabalho semelhante a:

delete_array(obj * ptr)

{

size_t *tmpptr = ((size_t *)ptr) - 1;

int num_elements = *tmpptr;

for (int i=num_elements-1; i>=0; i--)

{

call_destructors(ptr[i]);

}

free(tmpptr);

}

Traduzindo, ele vai chamar o destrutor de todos objetos, e vai executar num_elements vezes, posteriormente vai chamar free para marcar a área alocada como livre novamente. Lembrando que não é garantido que somente um índice, o num_elements, seja mantido, alguma implementação pode manter duas listas separadas, uma para descrever quantos bytes foram alocados e outra para manter a contagem de quantos objetos estão em memória, facilitando a abstração do código. O delete vai operar de forma semelhante porem não vai procurar uma lista de objetos para serem removidos. Ele vai operar sobre um único objeto. Ai vem a pergunta, porque não usar delete[] quando o ponteiro tiver sido alocado com new? A resposta é simples e complicada ao mesmo tempo hehehe, quando new aloca um objeto ele não necessita marcar quantos objetos foram alocados, ou seja, não necessita criar uma sentinela de quantos objetos foram criados, dependendo da implementação somente a lista de bytes alocados mantida pelo subsistema do SO já baste para o caso new/delete. Isso é possível já que se trata de apenas de um objeto. Isso salva espaço no seu executável final e algumas instruções a menos podem significar algum ganho de velocidade, ok e daí? Daí que se você chamar delete[] é bem provável que ele vá procurar a sentinela que mantém a quantidade de objetos alocados e vai encontrar lixo. Uma vez que ele vai encarar esse lixo como um valor que corresponde à quantidade de objetos alocados ele pode sair chamando o destrutor infinitamente ou simplesmente quebrar o processo ou quem sabe fazer o seu vizinho te perseguir com uma serra elétrica, enfim o resultado é o que chamam de UB (undefined behaviour). O mesmo vale para o caso onde você aloca um array com new[] e chama delete, alguns dirão que ele vai liberar somente o primeiro item e liberar a memoria total, mas na verdade é UB, não é garantido que isso vá acontecer. Então, sabendo disso, mantenha o motto na cabeça só que agora sabendo ;)

That’s all folks!

E lá vamos nós!

Ok, resolvi, sei lá pq, que vou compartilhar um pouco das maluquices que aprendi na minha vida como programador, digo vida como programador pq minha vida é basicamente isso hauwhauwhauwhaw É quase certo que esse material será melhor assimilado pra quem saca c/c++, mas quando der na telha falo sobre java pascal assembly brainfuck e outras linguagens bacanas :) e vamos nós!!!!