Compatibilidade Binária em C

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.

Hello World orientado a objetos

Hoje em dia é raro um desenvolvedor conhecer bem apenas uma linguagem. Em nome da multidisciplinaridade, decidi mostrar algums exemplos do mesmo programa: uma classe HelloWord com um construtor que recebe uma mensagem (“ola mundo”) e um método show() que imprime essa mensagem.

Vejamos um exemplo em C++

#include <iostream>#include <string>using namespace std;

class HelloWorld{        string mensagem;        public:        HelloWorld(string mensagem) { // construtor!                this->mensagem = mensagem;        }

        void show() { // metodo show                cout << "mensagem: " << this->mensagem << endl;        }};int main(){        HelloWorld obj("ola mundo");        obj.show();        return(0);}

Agora o mesmo código em Java:

package Hello;

public class HelloWorld{        private String mensagem;        public HelloWorld(String mensagem){                 this.mensagem = mensagem;        }        public void show() {                 System.out.println("mensagem: " + mensagem);        }        public static void main(String [] args){                HelloWorld obj = new HelloWorld("ola mundo");                obj.show();        }}

Agora, percebam a simplicidade do Ruby:

class HelloWorld        def initialize(mensagem)                @mensagem = mensagem         end        def show                 puts "mensagem: #{@mensagem}"        endend

obj = HelloWorld.new "ola mundo"obj.show

Agora em Perl (sim, Perl!)

package HelloWorld;

sub new{        my ($class, $mensagem) = @_;        my $self = {  mensagem => $mensagem  };        bless $self, $class; # abençoando a variavel!        return $self;}

sub show{        my $self = shift; # sim, ela vem como argumento!        print "mensagem: " . $self->{mensagem} . "\n";}

my $obj = new HelloWorld("ola mundo");$obj->show(); # parece artificial, não?

1;

PHP também é:

<?php

Class HelloWorld{        var $mensagem = "";        function HelloWorld($mensagem){                $this->mensagem = $mensagem;        }        function show(){                echo "mensagem : " . $this->mensagem . "<br/>";        }}

$obj = new HelloWorld("ola mundo");$obj->show();?>

Por fim, vamos criar um tipo usando JavaScript

function HelloWorld(mensagem){        this.mensagem = mensagem;        this.show = function(){                alert("mensagem: " + this.mensagem);        }}

var obj = new HelloWorld("ola mundo");obj.show();

Edit: Python, atendendo a diversos pedidos

class HelloWorld:    def __init__(self, arg):        self.message = arg

    def show(self):        print "mensagem: ", self.message

hello = HelloWorld("Ola Mundo")hello.show()

A diferença entre uma linguagem e outra está (entre outras coisas) na sintaxe: em Perl eu tenho que criar os mecanismos na mão (com bless e receber $self) mas em Java e Ruby é tudo muito natural. Em Javascript eu não tenho uma construtor propriamente dito (muito menos classes) mas uma função (e crio um objeto dando um new nessa função).

Vou abordar o tema “herança” no proximo post, espero que seja útil :)

Programando em C para console – parte 1

(Depois de um fecundo primeiro de Abril… )

Cada vez mais eu me espanto com a quantidade de novatos que tentam fazer programas para o “modo console” que não fazem o menor sentido.

Programas com menuzinho (1 – saque , 2 – deposito, 3 – sair) ou do estilo “digite um numero (q para sair)” são totalmente contra-produtivos. O novato perde TEMPO fazendo um monte de código desnecessário quando esquece o que deveria fazer (o tal do algoritmo).

Veja um exemplo: um programa que receba uma quantidade variavel de números e que imprima a soma dos mesmos. IMHO a forma mais interessante para fazer isso é pegar os números como argumentos do programa principal. Vc cria um programa console absolutamente limpo e facil de scriptar, por exemplo.

Veja só:

#include <stdio.h>

int main(int argc, char *argv[]){        double soma = 0.0;

        printf("A soma dos numeros eh %g\n",soma);

        return 0;}

Feito esse simples arquivo, vamos compilar

$ gcc -Wall soma.c$ ./a.out 1 2 3 4 5 A soma dos numeros eh 0

Ai vc fala “po, ta errado!”, mas claro, eu primeiro escrevi a base do meu programa. Perceba que eu compilei com a opção -Wall, que me informa todos os warnings que a compilação pode gerar (o que sempre é uma excelente pratica, eu procuro compilar sempre com 0 warnings).

Feito isso, vamos definir 2 coisas:
– O algoritmo da soma e
– Possiveis fluxos de excessão.

Vou partir do suposto que, se vc digitar alguma coisa que não seja um numero, eu vou ignorar e considerar como 0. Caso eu não informe nenhum numero ao programa, isso significa que estou faltando com alguma coisa, nesse caso vou informar uma mensagem elucidativa sobre o uso do programa.

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

int main(int argc, char *argv[]){        int i;        double soma = 0.0;

        if(argc == 1){                fprintf(stderr,"Faltando Argumentos!\n");                fprintf(stderr,"Uso: %s x1 [ x2 .. xN ]\n",argv[0]);                return 1;        }

        for(i=1;i<argc;i++)                 soma += atof(argv[i]);

        printf("A soma dos numeros eh %g\n",soma);

        return 0;}

Todos os argumentos que eu passo para o programa, incluindo o nome do mesmo, ficam no vetor argv, e o número de argumentos fica na variavel arcc. Dessa forma, o nome do programa é o argv[0] e, se eu não passar nenhum argumento adicional, argc será 1. A função atof converte a string para um número de ponto flutuante e retorna 0 se não for possivel converter.

Simples, não? Agora vamos executar:

$ ./a.out          Faltando Argumentos!Uso: ./a.out x1 [ x2 .. xN ]$ echo $?1$ ./a.out 1 2 3 4 5A soma dos numeros eh 15$ echo $?0

A mensagem de uso utiliza uma notação no manual de alguns programas do mundo unix: colocar os argumentos opcionais entre colchetes. Eu informo que preciso de pelo menos um argumento. Caso eu entre no fluxo de excessão, o meu main retorna um valor diferente de 0 para o sistema operacional que pode ser capturado pela variavel de ambiente $? (nesse caso estou em um linux utilizando o bash, em outros sistemas mais exotéricos como o Windows eu não imagino como vc poderia capturar essa informação, tampouco imagino se ela seria util).

Perceba que eu posso criar um script extremamente simples para utilizar esse programa, assim como testar o resultado é uma tarefa de um grep (ou diff). Eu poderia juntar um script de teste e um makefile nesse post mas estou esperando um exemplo um pouco mais interessante.

Eu criei um programa sucinto, facil de entender e muito util, sem ter que perguntar para o usuario nada. transformar esse programa num que calcula a média, por exemplo, é uma tarefa de colocar apenas uma linha (na verdade uma subtração e uma divisão) a mais. Espero que os programadores novatos se inspirem nessa forma de pensar e que utilizem os programas básicos do unix como o grep, cat, diff, cp, etc, para seus futuros trabalhos.