Membros privados em estruturas C

C/C++, Dicas, Linux 6 Comments »

Esta semana, lá no trabalho tive mais uma prova que paradigma de programação é algo completamente independente de linguagem, ou seja, não é pelo fato de você estar programando em C++, compilando com o g++ que o seu código vai ser orientado a objetos, tão pouco, se você programa em ANSI C o seu código obrigatoriamente vai ser estruturado ou você estará impedido de programar orientado a objetos.

Estou lendo certo, Orientação a Objetos em ANSI C?

Sim e não!

Coisas como herança, polimorfismo e sobrecarga são complicadas de fazer/emular em C, mas você pode programar utilizando um estilo que se comporte de forma semelhante à orientação a objetos. A libdfb é escrita em C mas “orientada a objetos”, de forma que você cria, manipula de destrói elementos que se comportam de forma bem semelhante a objetos.

Aqui no trabalho, temos um sistema de abstração de hardware em C que se conecta com uma GUI em C++. A camada mais baixa, em C também foi escrita (e muito bem escrita, diga-se de passagem - não por mim ) com essas técnicas, simulando uma orientação a objetos. Uma dessas técnicas, me chamou atenção por usar uma daquelas notas de rodapé dos livros de C.

Ao compilar código em C, cada símbolo só tem visibilidade dentro da unidade de compilação na qual ele foi declarado, a menos que seja declarado novamente nas outras unidades como extern. Dessa forma, é possível “esconder” certos símbolos dentro de sua unidade de compilação, tornando-os inacessíveis ao mundo exterior. Temos com isso encapsulamento.

A unidade de compilação é o conjunto de arquivos que depois de pre-processados e compilados geram um único código objeto. Basicamente (mas não exatamente), podemos tomar como unidade de compilação cada arquivo de implementação de fonte (*.c, *.cpp. etc). Maiores detalhes sobre as etapas de compilação em C e C++ podem ser encontrados no blog do Caloni.

Utilizando essas informações, podemos criar uma struc em ANSI C na qual os seus membros internos são “privados”. A mágica está em aprisionar a definição dos membros dentro da unidade de compilação e criar métodos de acesso para esses membros. Para isso usamos as notas de rodapé que nos mostram a diferença entre declaração e definição de elementos em C:

Declaração ou manifesto: Apresenta ao compilador um identificador sem dizer muito sobre seu significado, ou seja, diz ao compilador que o identificador XXX existe, mas pouco se sabe sobre o que ele representa.
Ex.:

extern int a;
void bla(void);
struct st_data;

Definição ou implementação: Diz ao compilador o que determinado identificador representa, como por exemplo quanto de memória deve ser alocada para ele e qual o endereço de memória onde podemos encontra-lo.
Ex.:

int a;
void bla(void) {/* Do anything.  */}
struct st_data {/* Some members. */}

Quando falamos de estruturas e tipos, sem a definição o compilador não tem como alocar memória para ele pois ele nada sabe a respeito de qual o espaço que uma variável daquele tipo precisa. Por outro lado, algumas vezes, sem a declaração, o linker não tem como saber que aquele símbolo existe.

Quando declaramos uma estrutura num cabeçalho, normalmente nós também definimos seus membros ali mesmo e toda vez que adicionamos esse cabeçalho a um fonte nosso, nós incluimos na unidade de compilação desse fonte tanto a declaração quanto a definição dessa estrutura, tornando os membros da estutura públicos à essa unidade de compilação. Isso nos permite acessar seus membros diretamente.

Se separarmos a declaração da definição, somente símbolo que representa a estrutura estará disponível, mas não os seus membros. Assim, qualquer tentativa de acesso direto a um membro, gerará um erro de compilação. Um efeito colateral interessante é que como o compilador nada sabe sobre o tamanho da estrutura, não será possível definir diretamente uma variável do tipo da estrutura, somente um ponteiro para ela, pois ponteiros têm todos o mesmo tamanho e o compilador precisa apenas do nome símbolo do tipo para criar o ponteiro.

Temos então o header mytype.h mais ou menos assim:

#ifndef MY_TYPE_H
#define MY_TYPE_H
 
/* O typedef é apenas pra não ficar repetindo a palavra struct. */
/* A declaração é somente o trecho:                             */
/* struct _mytype                                               */
typedef struct _mytype my_type;
 
void create_my_type( my_type** );
void destroy_my_type( my_type** );
 
void set_data( my_type* , int );
int get_data( my_type* );
void set_text( my_type* , const char* );
char* get_text( my_type* );
 
#endif

A implementação mytype.c:

#include "mytype.h"
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct _mytype {        /* Aqui fica a definição da estrtura. */
    int data;           /* Somente depois disso é que o com-  */
    int text[21];       /* pilador vai saber como alocá-la.   */
};                      /* Tente um sizeof(my_type) no main.  */
 
void create_my_type( my_type** my_ptr )             { /* some code... */ }
void destroy_my_type( my_type** my_ptr )            { /* some code... */ }
void set_data( my_type* my_ptr , int d )            { /* some code... */ }
int get_data( my_type* my_ptr )                     { /* some code... */ }
void set_text( my_type* my_ptr , const char* text ) { /* some code... */ }
char* get_text( my_type* my_ptr )                   { /* some code... */ }

O código fonte completo do exemplo pode ser encontrado aqui.

Como não é possível criar diretamente variáveis desse tipo, precisamos definir um “construtor” e um “destrutor”.

Tentativas de acesso direto a membros geram erro de compilação:

user@host:~/private-struct-members$ gcc -o teste mytype.h mytype.c main.c
main.c: Na função ‘main’:
main.c:22: erro: dereferencing pointer to incomplete type
user@host:~/private-struct-members$

Dessa forma, com um pouco de criatividade e tendo os conceitos tanto da linguagem quanto dos paradigmas, é possível implementar códigos realmente interessantes. Neste exemplo bobo talvez não tenha ficado clara a utilidade de forçar uma emulação de encapsulamento ou o uso de construtor/destrutor em C, mas em sistemas onde as circunstâncias não permitem um C++, ou que a complexidade tenda a atingir níveis críticos, essas técnicas se mostram de grande valia. No nosso caso, essa técnica especificamente, permitiu que um programador experiente, que não participou do projeto todo, descobrisse que sua tentativa de acesso direto a um membro de uma estrutura, estava contextualmente inadequada. Sem isso, um bug cabuloso de lógica iria aparecer somente em tempo de execução, provavelmente fazendo o software explodir na cara do cliente.

Links úteis (ou não…):
http://www.directfb.org
http://www.caloni.com.br
http://www.numaboa.com.br/informatica/c/
Livro Desenvolvimento do Kernel do Linux

Eclipse + Qt + svn

C/C++, Dicas, Qt, Subversion No Comments »

Mais como log pessoal do que um tutorial, senti a necessidade de deixar num local de fácil acesso, as etapas de configuração do eclipse para meu próprio uso, ou seja, a instalação do eclipse e dos plugins que eu normalmente uso (svn e qt).

Os comandos que afetam diretórios fora de seu home, precisarão de permissão de superusuário.

Se você resolveu instalar o eclipse apartir dos pacotes disponíveis em sua distribuição, pode pular direto para o passo #2.

Passo #0: Instalação do Java

Para que o eclipse funcione, vc precisa do Java Runtime Environment (JRE) instalado *E* configurado.

O JRE pode ser baixado direto do site da Sun http://java.sun.com/javase/downloads/index.jsp. Instruções completas de instalação e configuração podem ser encontradas em http://java.sun.com/javase/6/webnotes/install/index.html, para o caso do JRE 6.

Passo #1: Donwload e instalação do eclipse

O eclipse pode ser facilmente baixado a partir da área de downloads no seu site oficial: http://www.eclipse.org/downloads. Após o download, basta descompactá-lo e sair usando. No meu caso eu usei:

user@host$ tar -xvzf eclipse-cpp-europa-winter-linux-gtk.tar.gz -C /usr/local

Se tudo deu certo o executável do eclipse estará em /usr/local/eclipse/eclipse, daí é só criar um link/atalho/whatever no seu ambiente gráfico favorito.

Passo #2: Download e instalação do plugin de integração com o QT


Qt Eclipse Integration
Qt Eclipse Configuration

O plugin que eu atualmente uso é o fornecido pela própria Trolltech. Nele você pode gerenciar os seus arquivos de projeto a partir de um pequeno editor gráfico, e ainda se preferir, tem acesso direto ao arquivo .pro.

O download pode ser feito a partir de http://trolltech.com/developer/downloads/qt/eclipse-integration-download, e a instalação cujas instruções completas podem ser encontradas em http://trolltech.com/developer/downloads/qt/qteclipse-installmanual, é complicadíssima:

user@host$ tar -xvzf qt-eclipse-integration-linux.x86-gcc3.3-1.4.0.tar.gz -C /usr/local

Se tudo deu certo, os arquivos do plugin foram copiados para o diretório /usr/local/eclipse/plugins.

Nota: Para quem optou pela instalação do eclipse através dos pacotes da distribuição, atenção!!! Dentro do arquivo compactado, há o diretório eclipse/plugins/, e dento dele os arquivos do plugin, que devem ser copiados para o diretório de plugins da instalação do seu eclipse, normalmente /usr/lib/eclipse/plugins.

Após a instalação, inicie o eclipse com o comando:

user@host$ /usr/local/eclipse/eclipse -clean

Agora, vá em Window>Preferences>Qt e ajuste a versão e os “pathes”, de acordo com a sua instalação do Qt.

Para ficar mais cômodo, se você invoca o eclipse direto da linha de comando, adicione o diretório do seu executável na variável de ambiente $PATH, dentro de algum dos scripts de inicialização (.bash_profile)

export PATH=$PATH:/usr/local/eclipse

Passo #3: Subversion

No eclipse vá em Help>Softwares Updates>Find and Install. Marke a opção Search for new features to install e clique em Next. Agora adicione os sites remotos:

Buckminster
http://download.eclipse.org/tools/buckminster/updates

SubClipse
http://subclipse.tigris.org/update_1.2.x

Marque os respectivos checkboxes, e clique em Finish. Depois de uma pequena consulta à internet, é só marcar o plugin e dependências e correr pro abraço.

Iniciando no QT, parte III - qmake e .pro

C/C++, Qt No Comments »

Neste terceiro post sobre QT vamos falar do utilitário qmake e dos arquivos de projeto *.pro. Vamos entender para que serve o qmake e como configurar diferentes tipos de projetos.

O qmake

O qmake é um utilitário que acompanha o framework QT. Sua função é parsear um arquivo de projeto (*.pro) e gerar um Makefile já com as regras do moc, uic e opções do QT embutidas. Sem ele por exemplo, teríamos que chamar o moc explicitamente para criar os arquivos moc_* e passar explicitamente para o compilador e linker, as opções corretas para incluir o QT aos nossos projetos.

A documentação oficial sobre o qmake e arquivos de projeto, pode ser encontrada aqui.

Arquivos de projeto *.pro

Para criar um arquivo de projeto simples pela linha de comando, entramos no diretório do projeto e digitamos:

user@host$ qmake -project

Feito isso, será criado um arquivo com o mesmo nome do diretório corrente, seguido da extensão .pro. Caso já existam nesse diretório arquivos reconhecidos pelo qmake, como arquivos de códigos fontes (.h, .cpp), forms (.ui), etc, eles serão automaticamente adicionados ao arquivo de projeto.

Criado o arquivo .pro, ao executarmos qmake sem argumentos ele tentará parsear um arquivo .pro com o mesmo nome do diretório corrente. Você ainda pode especificar um arquivo de projeto alternativo como argumento para o qmake. Se tudo deu certo, um arquivo Makefile foi criado, e com um simples make, podemos compilar o projeto.

Os arquivos de projeto são arquivos de texto normais, contendo macros e diretivas que serão interpretadas pelo qmake para criar o Makefile. A lista completa de opções pode ser encontrada na documentação online. As mais comuns são:

TEMPLATE: Indicam o tipo de projeto. Use ‘app’ para proramas executáveis ou ‘lib’ para criar bibliotecas.

CONFIG: Adicionam opções diversas ao projeto. Entre elas, ‘debug’ para adicionar informações de depuração, ’staticlib’ em conjunto com o template ‘lib’, para que abiblioteca criada seja estática (.a no linux).

TARGET: O nome e a localização do alvo, ou seja, do aplicativo ou biblioteca.

MOC_DIR: Diretório onde serão grados os arquivos moc_*. Útil para não poluir o diretório de códigos fontes.

OBJECTS_DIR: Complementar à opção anterior, indica o diretório onde serão gerados os aquivos de código objeto (*.o).

INCLUDEPATH: Diretórios externos onde existem headers que serão utilizados no projeto, como headers de bibliotecas externas.

DEPENDPATH: Diretórios de códigos fontes externos que serão utilizados pelo projeto.

HEADERS: Os arquivos de cabeçalho do projeto (*.h).

FORMS: arquivos de interface gerados com o QtDesigner (*.ui).

SOURCES: Os arquivos de implementação de código fonte do projeto (*.cpp).

LIBS: Bibliotecas externas utilizadas pelo projeto. -L indica o path para a biblioteca, e -l diz o nome da biblioteca.

QT: Módulos do QT que devem ser adicionados/excluidos do projeto. Se for passado ‘QT =’ (QT igual vazio), nenhum módulo QT será utilizado no projeto.

SUBDIRS: Utilizado em conjunto com o template ’subdirs’, indica os subdiretórios ons o qmake deve procurar por outros arquivos de projeto.

Com isso em mãos podemos criar algums projetos simples.

Um aplicativo simples:

# O primeiro caracter desta linha cria um comentário
# O nome deste arquivo é 'app.pro'
TEMPLATE     =  app            # Nosso template é um aplicativo chamado
TARGET       =  bin/myapp.bin  # myapp.bin, dentro do dir ./bin
MOC_DIR      =  tmp/moc        # Diretório para mocs, opcional
OBJECTS_DIR  =  tmp/obj        # Diretório para código objeto, opcional
HEADERS      += myclass.h      # Header da classe MyClass
SOURCES      += main.cpp \     # Utilize \ para organizar os arquivos em
                myclass.cpp    # várias linhas.

Uma biblioteca simples:

# O nome deste arquivo é 'lib.pro'
TEMPLATE     =  lib            # Nosso template é uma biblioteca
CONFIG       += dll            # dinâmica, chamada
TARGET       =  lib/mylib      # mylib, dentro do dir ./lib
MOC_DIR      =  tmp/moc        # Diretório para mocs, opcional
OBJECTS_DIR  =  tmp/obj        # Diretório para código objeto, opcional
HEADERS      += myclass.h      # Header da classe MyClass
SOURCES      += myclass.cpp    # Implementação da classe MyClass

Um aplicativo que usa uma biblioteca externa:

# O nome deste arquivo é 'mixed.pro'
TEMPLATE     =  app            # Nosso template é um aplicativo chamado
TARGET       =  bin/myapp.lkd  # myapp.lkd, dentro do dir ./bin
MOC_DIR      =  tmp/moc        # Diretório para mocs, opcional
OBJECTS_DIR  =  tmp/obj        # Diretório para código objeto, opcional
INCLUDEPATH  += .              # Dir onde estão os Headers da lib externa
SOURCES      += main.cpp       # Implementação do aplicativo.

Diretórios aninhados:

Para utilizarmos diretórios aninhados, precisamos de um arquivo de projeto no diretório atual, e outro no subdiretório.

No diretório atual, utilizamos as opções:

TEMPLATE = subdirs
SUBDIRS  = [lista de subdirs para serem compilados em sequência]

Caso seja fornecido apenas o nome do diretório, ele precisa conter uma arquivo de projeto com o seu próprio nome. É possível no entando, informar diretamente na lista de subdiretórios, um diretório seguido por um arquivo de projeto com quaquer nome.

O código fonte completo dos exemplos abordados neste tutorial pode ser encontrado aqui.

Concluindo

O qmake é uma ferramenta simples e muito poderosa que facilita enormemente a vida de quem tem de gerenciar aŕvores de projeto complexas, mesmo que o projeto não utilize QT (basta acrescentar no final a opção ‘QT = ‘). Adicione a isso o fato de os arquivos de projeto terem uma sintaxe bastante simples e até certo ponto intuitiva. Se você precisar de ajustes mais finos, a documentação online lhe dará uma dezena de opções para se divertir.

Com QT ou sem QT, o qmake é sempre uma boa opção para gerenciamento de árvore de build.