Compatibilidade Binária em C

19 de novembro de 2009

Programar em C é sempre divertido, principalmente se vc sabe o que faz. Um exemplo disso é como trabalhar com estruturas de dados complexas, ponteiros e casting.

Imaginem as duas estruturas abaixo:

typedef struct {
	int id;
	char name[128];
} tpessoa;
 
typedef struct {
	int id;
	char name[128];
	char rg[128];
} tpessoafisica;

Ok, tenho um tipo tpessoa e um tpessoafisica que representam um tipo básico (pessoa) e um tipo propositalmente extendido, especializado para algum fim (pessoa fisica). Posso ter um tipo para pessoa juridica, por exemplo.

Imagine que eu posso ter diversas operações com o tipo básico e, por acaso, quero utilizar também com o tipo extendido (tpessoafisica). Como fazer? Em algumas linguagens eu posso fazer isso:

tpessoafisica x = {...};
tpessoa y = (tpessoa) x;

Entretanto em C isso gera um erro de conversion to non-scalar type requested. Eu posso converter int para float, float para int, int para long, char para int, etc, mas conversão de estruturas não é bem por ai: até porque não existe uma clara noção do que deveria acontecer, certo?

Para isso temos que clamar pelo conceito de compatibilidade binária: Sendo duas estruturas de dados, A e B, se B especializa A de forma ter todos os mesmos atributos na ordem que foi definida em A (e, opcionalmente, alguma coisa a mais no final), eu posso fazer um cast de um ponteiro do tipo B para um ponteiro do tipo A.

Vejamos, o tpessoafisica tem no começo os mesmos atributos (id e name) que a tpessoa e, por acaso, tem um atributos rg a mais no final. Dessa forma eu posso fazer o cast dos ponteiros na ordem apropriada.

void mostra_pessoa(tpessoa *x){
	printf("Pessoa { id = %d, name = %s }\n",x->id,x->name);
}
 
int main(){
	tpessoafisica x = {100, "pacman", "666"};
	// cast vale para ponteiros, por isso uso o operador &
	mostra_pessoa((tpessoa *) &x);
	return 0;
}

Ou seja, mostra pessoa esta preparado para receber um ponteiro do tipo tpessoa mas, graças a um habil cast de ponteiros aproveitando o principio de compatibilidade binária eu posso passar o endereço de uma estrutura diferente, no caso de tpessoafisica.

Perceba que eu preciso de um cast entre ponteiros, por isso eu preciso apelar para um & na frente da variavel, pegando o endereço de memória associado aquela variavel. Este recurso é util em muitas situações, desde simular interfaces e herança até coisas mais divertidas como fazer perl 5.x rodar perl 6.

Rating 4.50 out of 5
[?]

Brincando com a libavcodec e libavformat

21 de outubro de 2009

Vou contar uma histórinha. Pesquisando sobre http streamming para iPhone eu cheguei ate esta solução open source que utiliza, entre outras coisas, o ffmpeg. Eis que chego até este interessante trecho:

If you are interested in how the segmenter works you can find out more on how to use libavformat at the following resources: an older libavformat tutorial, some sample libavformat code, How to Write a Video Player in Less Than 1000 Lines, and more sample libavformat code.

Ora… não pensei duas vezes e cliquei no link sobre escrever um video player, curioso que sou. É um tutorial bem proveitoso,
que ja dá frutos na primeira lição. Deixo aqui um pequeno exemplo que analisa arquivos de video e informa dados do codec de cada stream (audio, video e o que mais tiver la dentro).

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
 
int main(int argc, char **argv){
   int i;
   char buf[256];
   AVFormatContext *pFormatCtx;
 
   av_register_all();
 
   while(*(++argv)){
     printf("analisando arquivo '%s'\n",*argv);
     if(av_open_input_file(&pFormatCtx, *argv, NULL, 0, NULL)!=0 || 
	av_find_stream_info(pFormatCtx)<0){ 
		puts("nao foi possivel analisar este arquivo!");
		continue;
	}
     for(i=0;i<pFormatCtx->nb_streams;i++){
	avcodec_string(buf, sizeof(buf), pFormatCtx->streams[i]->codec, 0);
	printf("\tstream[%d]=%s\n",i,buf);
     }
   }
 
   return 0;
}

Para compilar basta adicionar a libavcodec e a libavformat (apt-get nelas).

gcc -lavformat -lavcodec -Wall a.c

A execução é simples:

$ ./a.out *
analisando arquivo 'teste.bin'
	stream[0]=Video: wmv2, yuv420p, 320x240
	stream[1]=Audio: mp3, 48000 Hz, stereo, s16, 128 kb/s

Não apenas voltei a programar em C (que é uma delícia) como produzi um executavel bem enxuto (12 k) e util para determinar os codecs presentes em dado arquivo. Divertido. Vamos ver o que é possivel fazer agora.

Rating 2.00 out of 5
[?]

Quando o FakeWeb deu um grande susto pt 2: o changelog

9 de julho de 2009

Como falei anteriormente, o FakeWeb nos deu susto.

Olhando no CHANGELOG na versão que estavamos acostumados a usar o correto era fazer :string => “mensagem que veio no ‘body’” ,entretanto nas versões mais atuais isso foi substituido por :body => ‘tcharam’ … e é este exemplo que esta no site. Como isso quebra o backward compatibility, foi documentado no changelog mas passou despercebido. É claro que a versão mais antiga não vai funcionar que nem a nova.

Se tentassem executar os testes antigos no fakeweb 1.2.4 receberia
esta exception:

Deprecation warning: FakeWeb’s :string option has been renamed to :body.
Just replace :string with :body in your FakeWeb.register_uri calls.

Este é um exemplo onde mudar a forma como utilizamos uma biblioteca ou qualquer software causam todo o tipo de problemas quando não prestamos atenção a todos os detalhes.

Rating 4.00 out of 5
[?]

Comportamento estranho do FakeWeb versões antigas

9 de julho de 2009

Ontem fomos surpreendidos por este comportamento estranho do FakeWeb (versões 1.2.2 e 1.2.3), de uma hora para a outra o corpo das requisições vinham como “” (vazio).

irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'fakeweb'
=> true
irb(main):003:0> FakeWeb.register_uri(:get, 'http://google.com', :body
=> 'google')
=> [#<FakeWeb::Responder:0xb78290a8 @uri="http://google.com",
@options={:body=>"google"}, @method=:get, @times=1>]
irb(main):004:0> HTTParty.get('http://google.com')
=> nil
irb(main):005:0> x = HTTParty.get('http://google.com')
=> nil
irb(main):006:0> x.code
=> 200
irb(main):007:0> x.body  # deveria vir 'google'
=> ""
irb(main):008:0> Net::HTTP.get(URI.parse("http://google.com"))
=> ""  # mesma coisa... :/

Atualizando para versões mais recentes (como a 1.2.4) o problema acaba. Alguem mais passou por isso?

O FakeWeb é uma excelente ferramenta para testes reais onde dependemos da resposta de outros servidores. Ao inves de estabelecer uma comunicação real, que pode trazer outros tipos de problema de dificil detecção, simulamos a resposta para ver se o sistema se comporta de acordo com o esperado.

Sabe quando vc tem que testar se de 1, N, 0 elementos ou 404, essas coisas, e nem sempre vc pode forçar estas situações por ser complicado demais? Nesse ponto o FakeWeb é A ferramenta certa.

Rating 4.00 out of 5
[?]

Não entendo o motivo pelo qual ainda ensinam pascal

6 de julho de 2009

Encontrei este exercicio em um forum de programação e informatica. Um tanto engenhoso, devo admitir, e é otimo para praticar diferentes linguagens de programação, que podem exercitar muitos paradigmas. Infelizmente o professor pediu para fazer em Pascal.

o número 3025 possui a seguinte caracteristica: 30+25=55 e 55*55=3025. Escreva um programa que escreva todas os números com quatro algarismos que possuem a citada características.

Veja que solução elegante temos com ruby:

>> (1000..9999).find_all{|x| (x/100+x%100)**2 == x}
=> [2025, 3025, 9801]

Entretanto tive a apelar para uma sujeira (alguem percebe?) na versão em haskell:

Prelude> import List
Prelude List> findIndices(\x -> ((x `div` 100)+(x `mod` 100))^2 == x && x > 1000)[0..9999]
[2025,3025,9801]

Por fim, um simples ‘one liner’ usando gawk

$ seq 1000 9900 | gawk '(int($1/100) + $1%100) == sqrt($1){print}'
2025
3025
9801

É claro que a solução esperada é algo como

#include <math.h>
#include  <stdio.h>
int main(){
	int i;
 
	for(i=1000;i<=9999;i++){
		if(((i/100)+(i%100))==sqrt(i))
			printf("%d\n",i);
	}	
 
	return 0;
}

Até ANSI C é mais elegante – sem flamewars por favor, afinal todos sabem o que são programadores de verdade :)

Rating 4.50 out of 5
[?]

Sempre defina a forma de abertura de arquivos

5 de julho de 2009

Linguagens script tornam tudo muito facil. Codificar uma informação para base64, por exemplo, pode ser feito assim:

   require 'base64'
   data = "Now is the time for all good coders\nto learn Ruby"
   Base64.b64encode(data)

Entretanto se vc quer processar o conteudo de um arquivo, vale um cuidado extra: definir a forma de abertura dos arquivos.

Por exemplo, eu posso fazer isso

   require 'base64'
   data = File.open('imagem.jpg').read
   Base64.b64encode(data)

Se eu estou desenvolvendo no linux, para mim o resultado é coerente. Eu ficaria feliz (sem sacanagem) se fosse o caso de desenvolver um software que rodasse exclusivamente nesse sistema operacional. As vezes não é bem assim…

Ruby é um bom exemplo de uma linguagem que permite desenvolver software multi-plataforma, entretanto é muito facil atrapalhar o interpretador (afinal, quem nunca teve problema com uma referência a um arquivo “C:\xxx”?). No exemplo real que eu passei, bastaria informar o modo ‘rb’ (r de read, b de binary) para o comando open que eu teria o mesmo comportamento em todas as plataformas.

   require 'base64'
   data = File.open('imagem.jpg','rb').read
   Base64.b64encode(data)

É claro que este tipo de comportamento pode ser verificado através de uma suite de testes em todas as plataformas-alvo, entretanto vale de alerta para estudarmos um pouco as caracteristicas das apis e bibliotecas nas diferentes plataformas (não adianta, desenvolver software signfica estudar constantemente).

Para terminar, vejamos esta thread:

Summary: open should default to binary mode on windows

Initial Comment:
On windows the open() function defaults to reading
files in text mode. To get a binary mode file I need
to append a “b” to the mode string. I think this is an
unnessary platform inconsistency. Twice now I’ve had
hard to track down bugs because I was reading a file in
text mode and should have been using binary. This is a
wart, IMO.

Ou seja, muita gente, em diferentes linguagens, vai enfrentar o mesmo problema de achar que o open, pode padrão, vai abrir o arquivo em modo binario. A origem disso é antiga e não vai desaparecer de uma hora para outra.

Por fim, vale lembrar que o conceito de ‘nova linha’ é diferente de acordo com o sistema operacional, no unix é \n enquanto no windows é \r\n. Também é antigo e remonta a epoca de impressoras matriciais, DOS e por ai vai. É o tipo da coisa que, pela lei de murphy, vai sacanear a gente quando menos esperamos.

Obrigado ao Rafael por ter visto este detalhe comigo tarde da noite.

Rating 4.00 out of 5
[?]

Python é orientado à objetos. E bem orientado!

21 de maio de 2009

Quem sabe alguma coisa de Ruby ja se deparou com algumas caracteristicas da linguagens, como a não existencia de tipos primitovos: tudo é objeto, incluindo o numero 1.

Quando eu faço, em Ruby, algo como

i = 1 + 2

estou fazendo, na verdade,

i = 1.+( 2 )

Pois o 1 é um objeto da classe FixNum, que responde ao método + (simbolo de adição).

E no Python, sera que isso funciona? Na verdade a coisa é ligeiramente diferente!

i = 1 + 2

é equivalente à

i = (1).__add__(2)

É claro que vão falar: puxa, eu tenho que colocar os parentesis ao redor do 1 para invocar um método (que não tem o mesmo nome do operador + e sim algo bizarro como __XPTO__). Isso se deve à como o interpretador funciona, que vê o primeiro ‘.’ apos um numero como o delimitador da parte inteira da parte não-inteira.

É possivel, então, fazer a mesma coisa que eu faço com Ruby de duas formas:

i = int(1).__add__(2) # ou 
i = 1.0.__add__(2)

Percebam o 1.0.metodo tinhoso ali: eu estou invocando um método do objeto 1.0 – da mesma forma que o Ruby ou outras linguagens totalmente OO – apenas a sintaxe difere um pouco.

Rating 4.00 out of 5
[?]

Scrum na globo.com – derrubando mitos

9 de maio de 2009

Excelente apresentação do Danilo Bardusco sobre Scrum na globo.com

Scrum na Globo.com – Derrubando mitos – UPDATED

É a globo.com destilando o Scrum para todo   Brasil!

Rating 4.33 out of 5
[?]

Primeiro de Abril

1 de abril de 2009

Ano passado eu poste, inocentemente, que Java 1.7 iria ter ponteiros. E muita gente acreditou. Meses depois dessa postagem eu ainda recebia emails perguntando detalhes ou via em outros foruns gente desesperada (ou maravilhada) com essa feature.

Decidi não postar nenhuma pegadinha. De fato existe uma proposta de ter algo como os blocos unsafe do C# em java ( veja aqui) mas nada confirmado.

Bom primeiro de abril para todos!

Rating 2.33 out of 5
[?]

XStream e JSON

19 de março de 2009

XStream é a maneira mais fácil de trabalhar com XML em Java. Para trabalhar com JSON então, é ainda mais facil

XStream JSON Tutorial

Rating 3.00 out of 5
[?]