Matrizes Dinâmicas

Agosto 25, 2009

Voltando um pouco as bases vamos analisar neste artigo algumas possibilidades para criação de matrizes dinâmicas usando C++. Tem sido bem comum em fóruns e listas de emails o pessoal dar cabeçada com esse problema, neste artigo vou apresentar as soluções mais conhecidas e as características de cada uma.

Alocando cada Linha

A técnica que parece ser a mais popular é alocar um vetor de ponteiros e depois alocar uma linha da matriz para cada ponteiro deste vetor:


int main(int, char **)
{
    int nlinhas = 5;
    int ncol = 5; 

    int **mat = new int*[nlinhas]; 

    for(int i = 0;i < nlinhas; ++i)
        mat[i] = new int[ncol]; 

    //iniciando ela com zero
    for(int i = 0;i < nlinhas; ++i)
        for(int j = 0;j < ncol; ++j)
            mat[i][j] = 0; 

    //liberar memória
    for(int i = 0;i < nlinhas; ++i)
        delete []mat[i]; 

    delete []mat; 

    return 0;
}

O problema dessa técnica é o grande desperdício de espaço e tempo que ela gera. O desempenho desta é o pior, principalmente pelo fato de serem necessárias varias alocações e alocações de memória em C/C++ são operações que costumam ser bem caras.

Mesmo ignorando o tempo de criação da matriz, o uso dela também vai ser comprometido devido a possibilidade de cada linha dela estar numa região de memória diferente e é bem provável que o cache do processador tenha dificuldades em se manter atualizado por causa disso.

Já o desperdício de espaço ocorre devido a necessidade de se realizar varias alocações e é comum cada alocação precisa alocar um pouco mais de espaço do que o requisitado para a estrutura de controle usada internamente para gerenciar a memória.

A única vantagem em utilizar este método é quando precisamos de uma matriz absurdamente grande e alocamos cada linha apenas quando existe necessidade, mas mesmo nesse caso acredito que existam técnicas melhores para se implementar uma matriz esparsa.

Usando Apenas um Vetor

Uma maneira mais simples de se implementar uma matriz é utilizando apenas um vetor e realizar o acesso dos elementos como se fossem uma matriz:


int main(int, char *argv)

int main(int, char **)
{
    int nlinhas = 5;
    int ncol = 3; 

    //alocando a "matriz"
    int *mat = new int[nlinhas * ncol]; 

    //colocando 5 na linha 3, coluna 2
    mat[3 * ncol + 2] = 5; 

    //liberando a memoria
    delete []mat; 

    return 0;
}

Note como o código nesse caso é muito mais simples que o anterior, o único incomodo deste código é a necessidade de armazenar o tamanho da matriz (o número de linhas para ser mais preciso) e ficar passando ele como parâmetro a todo momento na hora de acessar um elemento, mas não é nada complicado criar uma classe para encapsular isso.

Para acessar um elemento usamos a fórmula: elem[linha * ncol + col], onde linha é o numero da linha que se quer acessar, ncol o número de colunas da matriz e col o número da coluna que se deseja acessar.

Como é feita apenas uma alocação é usado o minimo de memória possível, além de deixar o cache feliz na hora de acessar os dados.

Utilizando std::vector

Por fim, podemos utilizar o std::vector e não precisamos assim nos preocupar mais em gerenciar a memória da matriz e deixar o programa mais alinhado com o RAII:


#include <iostream>
#include <vector>

int main(int, char **)
{
    int ncol = 4;
    int nlinha = 3; 

    std::vector mat(ncol * nlinha);    

    //acessando elemento 2, 1 (linha 2, coluna 1)
    mat[2 * nlinha + 1] = -1; 

    std::cout << mat[2 * nlinha + 0] << std::endl;
    std::cout << mat[2 * nlinha + 1] << std::endl; 

    return 0;
}

Esse código é muito semelhante ao anterior, mas as operações de gerenciamento de memória foram encapsuladas com o uso de um std::vector, utilizando-se uma boa implementação de std::vector não deve existir diferença alguma de performance e uma diferença minima de consumo de memória entre este exemplo e o anterior.

Esta é a maneira mais simples que conheço para lidar com matrizes dinâmicas, mas o ideal mesmo seria construir uma template que encapsule estas operações, mas isto fica para um outro post.


Problemas com Memória

Julho 21, 2009

Neste artigo vamos discutir dois problemas comuns e causam um bocado de dores de cabeça e as vezes fazem a gente perder preciosas horas de sono, vamos então conhecer os memory leaks e dangling pointers.

Memory Leaks

Memory leak ou vazamento de memória é um problema um tanto comum em C++, principalmente em casos onde o programador tende alocar memória sem necessidade (o que tem se tornado bem comum com programadores que vem de linguagens onde existe um garbage collector) ou não gerencia esta corretamente esquecendo os princípios básicos do RAII.

O memory leak ocorre quando um bloco de memória alocado fica sem referências, isto pode ocorrer em programas bem simples, vejamos um exemplo:


int main(int , char **)
{
    int *p = new int;
    *p = 3; 

    p = new int;
    delete p; 

    return 0;
}

No exemplo acima, primeiramente é alocado espaço para um int e associado com o ponteiro p, logo após a atribuição do valor “3” é feita uma nova alocação e é neste ponto que o vazamento ocorre.

Note que a memória alocada anteriormente não foi liberada e agora o ponteiro p aponta para outro bloco de memória, a única variável que continha o endereço do primeiro bloco alocado não possui mais, sendo assim, o programa não sabe mais o endereço do bloco e não tem mais como liberar essa memória.

No final da execução desse programa o sistema operacional vai se encarregar de liberar toda a memória do programa, sendo assim este pequeno int que foi perdido não é problema, mas imagine o caso de um programa que tenha que funcionar em um regime 24×7, e a cada segundo ele perde uma centena de ints como aquele, nesse ritmo o programa perde 400 bytes por segundo (não contando aqui o overhead da alocação, que inclui as estruturas de controle do runtime do C++ e do SO).

Em uma hora este programa já vai ter perdido pouco mais de 1 mega de memória, em 24 horas já vão embora 32 mega de memória. Não parece grande coisa, ainda mais hoje em dia que até computador das Casas Bahia vem com 2 gigas de memória, mas lembre-se que esta memória poderia estar sendo útil para outros processos, e no caso de um sistema embarcado este problema é bem grave, um playstation 2, por exemplo, possui apenas 32 mega de ram, então em uma hora já acabamos com boa parte da memória dele.

Dangling Pointers

Os ponteiros “quebrados” costumam ser bem desastrosos e ocorrem quando um ponteiro aponta para uma região de memória “invalida”. Na verdade o ponteiro apontar para algo invalido não é problema, o problema ocorre quando é feita uma tentativa de acesso nesse endereço invalido, vamos a um exemplo básico:


#include <string> 

void func()
{
    std::string *str = new std::string("abc");
    delete str; 

    str->assign("bla bla");
} 

std::string *func2()
{
    std::string str; 

    return &str;
} 

int main(int , char **)
{
    func(); 

    std::string *str = func2();
    str->assign("ops");

    return 0;

}

A primeira função (a func) primeiramente aloca uma std::string, libera ela da memória e depois disso tenta acessa-la. Este é o caso mais comum de um dangling pointer, onde um ponteiro armazena um endereço para um bloco de memória que já foi liberado. Nesse exemplo é muito provável que nada demais aconteça pois o uso é feito logo após a desalocação, mas não podemos esquecer que após um delete estamos dizendo ao sistema que não vamos mais usar aquela memória e ele pode fazer o que bem entender com ela, isso significa que esta memória pode inclusive ser devolvida ao sistema operacional, que pode passar ela a outro processo, sendo que tudo isso pode acontecer antes mesmo da chamada de assign, e quando assign for executado ele vai tentar acessar a memória de outro processo e teremos um belo “ding, este processo executou uma …”.

Em programas mais complexos pode ser que ocorram novas alocações de memória antes que o dangling pointer seja usado, nesse caso aquele bloco que foi liberado já pode estar sendo usado por outro objeto e na chamada de assign estaremos acessando uma memória que pode conter qualquer valor, onde o resultado da operação é como diz a especificação “indefinido”.

A função func2 mostra um outro erro bem comum onde retornamos o endereço de uma variável local, nesse caso após a função retornar o destrutor da string vai ser chamado e sem falar que o endereço que ela “reside” já vai estar disponível para outros usos, novamente é bem provável que o sistema operacional se zangue com seu processo e mate ele.

Um detalhe sobre a segunda função é que muitos compiladores geram um warning quando o programador faz algo desse tipo, sendo assim este problema é fácil de ser evitado.

Evitando Esses Problemas

O jeito mais simples de evitar estes problemas é utilizar alguma técnica de smart pointer, com isso já é possível evitar os memory leaks (tem que apenas tomar cuidado com as referencias circulares).

Utilizando smart pointers é certo que um dangling pointer nunca vai ocorrer pois o smart pointer vai cuidar para que os ponteiros sejam sempre válidos (claro que pode ocorrer uma invasão de memória ou outro evento grotesco).

Infelizmente os memory leaks ainda podem ocorrer devido as referencias circulares e para tentarmos detectar estes precisamos usar outras técnicas, mas isto fica para um próximo post.


Compilando a Boost no Visual – 2009

Junho 3, 2009

O primeiro post do blog foi exatamente este, e não querendo já parecer sessão da tarde e começar a repetir tudo de novo estou re-fazendo o post.

O post original é bem incompleto em vários aspectos, e ao invés de atualizar ele decidi fazer um completamente novo aproveitando que estou configurando uma maquina de build.

Outra vantagem deste artigo é que indo direto a seção “Configurando o Visual”, as informações ali contidas servem para a maioria das bibliotecas de C/C++.

Baixando a Boost

Esta é a etapa mais simples de todas, basta acessar o site oficial, e clicar no link “Download” do lado direito da tela. A versão 1.39.0 (a ultima lançada até o dia que esse artigo foi publicado) pode ser baixada clicando aqui.

No meu caso utilizo a versão zip do pacote, mas todos os outros pacotes acredito que possuam o mesmo conteúdo.

Após o download extraia o conteúdo dos arquivos para o diretório de bibliotecas que você costuma utilizar, no meu caso c:\develop\libs.

Depois de extrair os arquivos é necessário baixar o bjam, que é a ferramenta de compilação da Boost, a ultima versão pode ser encontrada clicando aqui.

Após concluído o download do bjam extraia o arquivo exe para o mesmo diretório em que a Boost foi colocada  (no meu caso C:\develop\libs\boost_1_39_0).

Compilando

Agora com as ferramentas prontas é necessário rodar o bjam, para tal acesse o console do Visual Studio (não o console padrão do Windows) clicando em “Iniciar” –> Todos os programas –> Microsoft Visual Studio –> Visual Studio Tools –> Visual Studio 2008 Command Prompt.

Com o console aberto abra o diretório da Boost (no meu caso digito: cd \develop\libs\boost_1_39_0). Dentro do diretório da boost digite “bjam” e ENTER.

Agora como já diz o programa, paciência…

Se tudo correu bem, muitos minutos depois a compilação deve estar completa, as vezes aparecem alguns warnings, que eu simplesmente ignoro.

Configurando o Visual

O jeito mais pratico de configurar o visual na minha opinião é criando uma variável de ambiente que contenha o diretório da Boost, fica mais pratico na hora de atualizar as versões (se não quiser criar a variável, basta ir para o próximo paragrafo), então primeiramente clique em “Iniciar” –> Botão direito em “Meu Computador” –> Propriedades.  Na janela que se abrir, clique em “Avançado”, depois no botão “Variáveis de Ambiente”. Na caixa “Variáveis do Sistema”, clique em “Novo”. Agora entre com o nome (ex: BOOST_HOME), e o valor, que é o diretório onde a boost foi instalada (c:\develop\libs\boost_1_39_0 no meu caso), agora é ir clicando em Ok até fechar tudo.

O próximo passo é abrir o visual (se o seu visual já estava aberto e você criou a variável, feche ele e abra novamente). Dentro do visual acesse “Tools” –> “Options”, expanda a linha “Projects and Solutions” –> “VC++ Directories”.

Com a janela de configuração de diretórios podemos configurar os diretórios utilizados pelo visual durante builds, o primeiro a ser configurado é o de include (a ordem não importa aqui), então do lado direito, na caixa de seleção “Show directories for”, selecione “Include Files”. Basta clicar na primeira linha vazia após o ultimo item e entrar com o diretório, que no meu caso foi: $(BOOST_HOME). Caso eu não tivesse criado a variável, o valor teria sido: c:\develop\libs\boost_1_39_0.

Configurando diretórios de include

Por fim é necessário configurar o diretório de bibliotecas selecionando “Libraries Files” na caixa “Show Directories for” e entrando com o diretório das libs, que no meu caso foi: $(BOOST_HOME)\stage\lib, se não estiver usando a variável, basta então: c:\develop\libs\boost_1_39_0\stage\lib, note que o diretório varia de acordo com o diretório que foi usado na instalação, mas o principal é o sub-diretório “stage\lib”.

visual_boost_02

Tendo inserido os diretórios, clique em Ok para salvar.

Testando a Instalação

Para testar a instalação crie um novo projeto no Visual (se não sabe como fazer, clique aqui) e use um código exemplo da Boost, eu utilizei o código abaixo:


#include<boost/filesystem/operations.hpp>
#include<iostream>

namespace bf = boost::filesystem;
int main(int, char **)
{
    bf::path p("first.cpp");
    if(bf::exists(p))
        std::cout<<p.leaf()<<"exists.\n";
    else
        std::cout<<p.leaf()<< "does not exist.\n";

    return 0;
}

O código compilou sem problemas aqui e executou perfeitamente, sendo assim, instalação concluída.

Note que a Boost automaticamente já linka o código com as bibliotecas necessárias, com algumas bibliotecas é necessário configurar o arquivo lib a ser usado, mas isso fica para outro post.


Boost 1.39.0

Maio 4, 2009

Foi lançada a mais nova versão da Boost, desta vez inclui apenas uma lib nova, a Signals2, que é uma implementação de um sistema de sinais e slot, na verdade essa lib é uma implementação thread safe da Signals já existente. Além desta lib nova foram feitas diversas correções e melhoramentos nas libs existentes, maiores detalhes no release notes.


Como utilizar o Visual C++ – Parte 1

Março 6, 2009

O Visual Studio é um pacote de programas da Microsoft para desenvolvimento de software, suportando diversas linguagens como C#, C++, C, Java, Visual Basic, etc. Nesta série de artigos vou focar apenas no Visual C++ Express 2008, mas a maioria da dicas / comandos devem funcionar em outras versões do Visual C++.

As versões express do Visual Studio são versões grátis que a microsoft disponibiliza e obviamente elas não possuem todas as funcionalidades das versões pagas, mas na minha opinião o Visual C++ Express é a melhor ferramenta “grátis” para se trabalhar com C++ no Windows.

Instalando

O primeiro passo é instalar a ferramenta, e o jeito mais simples é indo ao site oficial: Visual C++ Express. Logo ali no lado direito tem um botão “Download Now”, escolha a linguagem (Inglês no meu caso) e clique em Download.

Após concluído o download, execute o programa para inicializar a instalação que a principio é como qualquer outro programa, aceitar licença, instalar algum atalho extra, etc.

A versão atual do instalador pergunta se você quer instalar o Microsoft Silverlight Runtime e o Microsoft SQL Server, nenhum dos dois é necessário, a menos que você queira usar o SQL Server como banco de dados para suas aplicações ou utilizar o Silverlight

Opcionais do Visual C++ Express

Após escolher os adicionais, vem a escolha do diretório de instalação, de novo, é a gosto do fregues. Clicando em “Next” é iniciado o download. Existe no site da Microsoft imagens de CD para quem quiser instalar em um computador que não tenha acesso a rede.

Rodando

Após concluída a instalação, o instalador cria um atalho no menu iniciar, basta então acessar ele e clicar no “Microsoft Visual C++ Express Edition 2008″. O Visual vai carregar e você deve ver uma tela parecida com a abaixo:

Tela inicial do Visual

A versão express costuma solicitar registro, basta seguir o link do dialogo que surgir, entrar com os dados e depois inserir a chave de registro no visual.

Criando a primeira Solução (projeto)

O Visual Studio gerencia o software criado através de soluções, cada solução possui um ou mais projetos, que formam o software ou o conjunto de software sendo criado.

Para criarmos nosso “Hello Visual”, clique em “File” -> “New” -> “Project”. Surge então um dialogo parecido com o abaixo:

Criando novo projeto

Clique em Win32, depois selecione “Win32 Console Application”. Entre com o nome do projeto, diretório onde ele vai ser criado, e nome da solução (que é opcional), em seguida clique em “Ok”.

Configurando o novo projeto

Surge então a primeira tela do Application Wizard, clique em “Next”, na segunda tela selecione “Console Application” e marque a caixa “Empty Project “. Dessa forma é criada uma aplicação vazia, e sem o código de “Hello World” do visual. Depois de criado, o projeto pode ser modificado para aplicação com janela, dll ou biblioteca.

Selecionando o tipo de projeto a ser criado

Agora clique em “Finish” e o projeto vai ser criado.

Após criada a aplicação, deve surgir então o “Solution Explorer”, que é uma janela (que costuma ficar do lado esquerdo da tela) com a visão de todos os projetos e arquivos da sua solução.

Visão do Solution Explorer

No nosso caso ela vai estar vazia, agora criaremos o primeiro arquivo de código: clique com o botão direito do mouse no nome do projeto (meuPrimeiroProjeto, no exemplo), selecione “Add” -> “New Item”. Na janela que aparecer, selecione “C++ File (.cpp)”, entre com o nome do arquivo e clique em “Add”.

Criando novo arquivo para o projeto

Vai surgir então um lindo arquivo em branco, repare no “Solution Explorer” que o arquivo foi adicionado ao seu projeto, agora basta entrar com o código do “Hello World”:

#include <stdio.h>
int main(int argc, char **argv)
{
    printf("Hello Visual");
    return 0;
}

Após entrar com o código, clique na opção “Build” (menu principal) e selecione “Build”, ou então pressione F7. Na parte de baixo da tela vai surgir a tela de output, que mostra o que o compilador esta fazendo, e no final do processo ela indica se houve algum erro ou não. Se aconteceu algum erro, dando um clique duplo sobre a mensagem de erro foca o mesmo na tela.

Programa de teste após compilação

Rodando o “Hello World”

Agora que o projeto já foi compilado, basta executar ele. No menu principal, selecione “Debug” -> “Start Without Debugging”, ou pressione “CTRL + F5″. Pronto, vai surgir uma janela de console com a saída do seu programa.

No proximo post, vamos aprender um pouco mais sobre o build do visual.


Strings e Números

Janeiro 23, 2009

No ultimo post mostrei as operações básicas com strings como contar caracteres e comparações, agora vamos nos aprofundar um pouco mais.

Primeiramente, como funciona um char? O char em C é um int pequeno (geralmente 8 bits) sinalizado (o char pode ser sinalizado ou não, dependendo da plataforma), a questão é que o char armazena números, e os números representam caracteres. Mas qual número é qual? O Visual (e acho que quase todos compiladores) usam como base a tabela ASCII que define o valor numérico de cada caracter.

Com base nisso, podemos então escrever:


#include <stdio.h>

int main(int, char **)
{
    char ola[4];

    ola[0] = 111;
    ola[1] = 108;
    ola[2] = 97;
    ola[3] = 0;

    printf(ola);

    return 0;
}

No código acima inicializamos a string “ola” usando os valores numéricos dela, mas qual a vantagem disso? Nenhuma, na verdade apenas facilitamos a vida do compilador, mas o que pode ser feito de útil com esse conhecimento?

A primeira coisa que pode ser feita é traduzir o outdoor da EA:

ea_canada_tbwa_vancouver_01

 

Agora vamos supor que seja necessário criar um programa para verificar se uma string possui apenas caracteres de A a Z (caixa alta):

#include <iostream>

bool ehAZ(const char *str)
{
    for(int i = 0;str[i]; ++i)
    {
        if((str[i] < 'A') || (str[i] > 'Z'))
            return false;
    }

    return true;
}

int main(int argc, char **argv)
{
    using namespace std;

    cout << ehAZ("ABC") << endl;
    cout << ehAZ("ABaC") << endl;
    cout << ehAZ("!@#$%") << endl;

    return 0;
}

Note que na tabela ASCII letras e números estão em sequência, por isso não precisamos nos preocupar em testar todas as letras, podemos testar apenas sequências.

Convertendo para Letras Maiúsculas

Agora vamos ver como fica uma rotina para mudar todas as letras de uma string para maiúsculas:

#include <iostream>

char *toUp(char *str)
{
    for(int i = 0;str[i]; ++i)
    {
        if((str[i] >= 'a') && (str[i] <= 'z'))
            str[i] = 'A' + (str[i] - 'a');
    }

    return str;    
}

int main(int argc, char **argv)
{
    char str[] = "abCd!@#$456z";

    using namespace std;

    cout << toUp(str) << endl;

    return 0;
}

A função toUp é bem simples, ela percorre a string procurando caracteres que estejam no intervalo de ‘a’ a ‘z’, e quando encontra converte o caractere para sua versão maiúscula. A formula de conversão  primeiramente calcula qual o índice da letra sendo convertida (que seria o número da letra no alfabeto, mas começando de zero) e depois soma esse índice ao índice inicial das letras maiúsculas.

Vamos pegar como exemplo a conversão da letra ‘c’, primeiramente é feito str[i] – ‘a’, o que resulta em ‘c’ – ‘a’, chegando em 99 – 97, resultando 2. Na sequência é feito ‘A’ + 2, que significa: 65 + 2 = 67. Consultando a tabela temos: 67 = C.

Apenas para lembrar, já existe uma função na biblioteca padrão para conversão de letras minusculas para maiúsculas chamada toupper, que é declarada em ctype.h. Neste header também existem outras funções do mesmo genero, como: tolower, islower, isupper, etc.

Convertendo para Números

Por fim, vamos ver como converter uma string que contêm um número inteiro para uma variável to tipo int:

#include <iostream>

int myAtoi(const char *str)
{
    int len = (int) (strlen(str)-1);
    int num = 0;
    int dec = 1;
 
    for(;len >= 0; --len)
    {
        if(str[len] >= '0' && str[len] <= '9')
        {
            num += dec * (str[len] - '0');
            dec *= 10;
        }        
        else if((len == 0) && (str[len] == '-' || str[len] == '+'))
            num = (str[0] == '-') ? -num : num;
        else
        {
            //numero invalido, entao 0
            return 0;
        }
    }    

    return num;
}

int main(int argc, char **argv)
{
    using namespace std;

    int num = myAtoi("23");

    cout << num << endl;
    cout << myAtoi("-10") + myAtoi("5") << endl;

    return 0;
}

A função myAtoi processa a string e retorna o int que ela representa, note que a função processa a string do final para o começo para facilitar os cálculos. No loop principal primeiramente é verificado se o caracter atual é um número (ou seja, tem que estar entre ‘0′ e ‘9′). Se for um número, a variável num é atualizada com a formula: dec * (str[len] – ‘0′). O código str[len] – ‘0′ calcula qual o valor do número atual (da mesma forma que foi feita na conversão de letras para caixa alta). Achado o número, multiplicamos ele por dec, que armazena qual a casa decimal que ele se encontra (unidade, dezena, centena, etc), e por fim o valor é somado com o número atual. Depois disso a casa decimal é atualizada (em dec *= 10).

Caso o caracter não seja um número, é verificado se a string contêm o sinal + ou – na primeira posição, caso sim, o sinal do número é atualizado de maneira adequada. Caso o caractere em questão não seja número ou sinal, a função retorna 0. O correto seria retornar algum erro, mas para não complicar o exemplo ficamos com o zero.

Na biblioteca padrão existem as funções atoi e strtol que fazem conversões de strings para números.

A conversão de int para string fica como lição de casa, uma boa leitura para se aprender mais é o artigo sobre tipos básicos do Caloni: Básico do básico: tipos.


Strings em C

Dezembro 11, 2008

Um item que vejo muitos novatos na linguagem tendo dificuldades são as strings em C, por isso resolvi escrever alguns posts e tentar resolver muitas das duvidas que vejo por ai.

Para começo de conversa strings em C praticamente não existem, o compilador tem apenas uma vaga noção do que é uma string, pois elas são uma convenção onde um vetor de caracteres terminado com 0 (zero) representa uma string. Sendo assim, podemos criar uma string usando:


#include <stdio.h>

int main(int, char **)
{
    char ola[4];

    ola[0] = 'o';
    ola[1] = 'l';
    ola[2] = 'a';
    ola[3] = 0;

    printf(ola);

    return 0;
}

Este código simplesmente imprime “ola” na tela, mas note que ao inicializar ola colocamos o caracter ‘\ 0′ (que nada mais é que o numero 0) no final do array, ele é necessário pois é a única forma do printf saber onde termina a string. Agora escrever esse código toda vez que precisarmos criar uma string é bem chato, por isso, uma das poucas coisas que o compilador C sabe sobre strings é:

#include <stdio.h>
 
int main(int, char **)
{
    char ola[] = "ola";

    printf(ola);

    return 0;
}

Este código é idêntico ao anterior, mas escrito de uma maneira bem mais simples. Note que o compilador reconhece a string “ola” e declara um array de 4 chars e já inicializa ela com a string “ola” (incluindo o ). Também podemos escrever:

#include <stdio.h>

int main(int, char **)
{
    const char *ola = "ola";

    printf(ola);

    return 0;

}

Este código já é um pouco diferente do anterior, aqui não criamos um vetor de caracteres, e sim um ponteiro para uma região de memória constante do tipo char, trocando por miúdos, criamos um ponteiro que aponta para string. Mas onde diabos foi parar a string? O compilador alocou um trecho de memória constante na seção de dados do código (uma variável global). Por isso usamos const, pois esta string não deve ser modificada pelo código.

Contando Caracteres

Agora que já sabemos como uma string funciona em C, vamos fazer uma função para contar quantos caracteres uma string tem, assim podemos ver como o 0 no final dela é usado:

#include <stdio.h>

int contaChar(const char *str)
{
    int i = 0;

    for(;str[i] != 0; ++i);

    return i;
}

int main(int, char **)
{
    char ola[] = "ola";

    printf("A string %s possui %d caracteres\n", ola, contaChar(ola));

    return 0;
}

 
Note que sempre que precisamos saber quantos caracteres uma string possui, precisamos percorrer toda a string, isso pode trazer problemas de performance em algumas aplicações, e isto ocorre com quase todas as operações com string.

 

Outro detalhe, não é preciso criar a função contaChar, quando precisar saber o tamanho de uma string basta usar a strlen, que é declarada no string.h:

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

int main(int, char **)
{
    char ola[] = "ola";

    printf("A string %s possui %d caracteres\n", ola, strlen(ola));

    return 0;
}

Neste mesmo arquivo existem varias outras funções para se trabalhar com strings, é recomendável dar uma olhada na documentação antes de escrever a sua própria função para verificar se ela já não existe (alias, isso é recomendável para qualquer coisa, não apenas strings).
 

Comparando Strings

Agora chegamos ao ponto onde a maioria tem dificuldades, como saber se uma string é igual a outra? A idéia inicial é escrever:

#include <stdio.h>

int main(int, char **)
{
    char ola[] = "ola";
    char ola2[] = "ola";

    if(ola == ola2)
        printf("Iguais");
    else
        printf("Nao sao iguais");

    return 0;
}

Se você entendeu tudo até aqui já deve imaginar qual vai ser a saída do programa acima, se não, tente ler novamente e execute o programa. Muitos ficam surpresos ao ver o programa imprimir “Não são iguais”. Isto ocorre porque o if esta na verdade comparando dois ponteiros (lembre-se, arrays sem índice são ponteiros).

Então como fazer para saber se duas strings são iguais? É necessário comparar todos os caracteres das strings:

#include <stdio.h>

bool saoIguais(const char *s1, const char *s2)
{        
    for(int i = 0;s1[i] == s2[i]; ++i)
    {                
        if(s1[i] == 0)
            return true;
    }
    return false;
}

int main(int, char **)
{
    char ola[] = "ola";
    char ola2[] = "ola";

    if(saoIguais(ola, ola2))
        printf("Iguais");
    else
        printf("Nao sao iguais");

    return 0;
}

Agora já sabemos como identificar se duas strings são iguais ou não, um pouco trabalhoso, mas é o único jeito. Para simplificar um pouco a vida, a biblioteca padrão já vem com uma função de comparação de strings chamada strcmp, que funciona de maneira similar a função saoIguais, a diferença é que essa função não verifica apenas se as duas strings são iguais, ela compara as duas, retornando 0 quando são iguais, -1 quando a primeira string vem antes da segunda, ou 1 caso contrário:

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

int main(int, char **)
{
    char ola[] = "ola";
    char ola2[] = "ola";

    if(strcmp(ola, ola2) == 0)
        printf("Iguais\n");
    else if(strcmp(ola, ola2) != 0)
        printf("Nao sao iguais");

    return 0;
}

Pronto, agora já temos uma maneira simples de verificar se duas strings são iguais ou não. 

No próximo post, vamos dar uma olhada em mais algumas operações com strings em C.


C++ Type Casting – 3ª e ultima parte

Novembro 5, 2008

No post anterior vimos como realizar as operações de casting usando os novos operadores do C++, neste post vamos ver como realizar casting com os smart pointers da Boost , estes operadores podem ser usados com o shared_ptr e o intrusive_ptr.

Mas primeiramente, porque existem operadores de cast específicos da Boost? Pelo fato de que os operadores do C++ não estão preparados para lidar com smart pointers e contagem de referências, dessa forma, caso se utilize um operador padrão do C++ a contagem de referência do objeto pode se tornar invalida, resultando em um objeto sendo destruído mais de uma vez. Exemplo:


enum Eventos_e
{
    TIRO,
    ABRIR_PORTA
};

class Objeto
{
    public:
        virtual void Evento(int tipo, void *param) = 0;
        virtual void Atualizar()=0;
};

class Monstro: public Objeto
{
    public:
        virtual void Evento(int tipo, void *param);
        virtual void Atualizar();
       
    private:
        int m_energia;
};

Com base na hierarquia acima, vamos criar alguns objetos e fazer um cast:


int main(int, char **)
{
    using namespace boost;

    shared_ptr<Objeto> obj(new Monstro());
   
    shared_ptr<Monstro> monstro(static_cast<Monstro *>(obj.get()));   
}

Repare que na linha onde é feito o cast é usado o método get do shared_ptr para se obter uma referência ao objeto que obj armazena, depois é feito o cast e o ponteiro resultante é utilizado para inicializar o ponteiro “monstro”, note que monstro foi inicializado com um ponteiro comum, não com outro shared_ptr, ou seja, para o shared_ptr “monstro” ninguém até o momento gerenciava esse ponteiro, então ele vai simplesmente começar uma nova contagem de referências, dessa forma, quando a função terminar de executar, ambos os smart pointers vão destruir o objeto Monstro, resultando em um comportamento indefinido.

Para evitar esses problemas foram criados os casts a seguir:

Cada um dos casts acima deve ser usado nas mesmas situações dos casts do C++, a diferença é que eles devem ser apenas usados quando os objetos envolvidos são gerenciados por smart pointers.

No caso do exemplo anterior, o código correto é listado abaixo:


int main(int, char **)
{
    using namespace boost;

    shared_ptr<Objeto> obj(new Monstro());
   
    shared_ptr<Monstro> monstro(static_pointer_cast<Monstro>(obj)));   
}

Note que as operações de cast da boost são idênticas as do C++, a diferença é o nome do operador usado.

As operações de cast da Boost devem ser sempre usadas quando é necessário fazer casts entre ponteiros gerenciados por smart pointers da mesma, nos outros casos, deve-se utilizar os operadores do C++.


Boost 1.37.0

Novembro 3, 2008

Noticia rápida, foi disponibilizado hoje uma nova versão da Boost, versão 1.37.0, que além de consertar bugs e adicionar algumas funcionalidades as bibliotecas existentes adiciona uma nova chamada Proto, que é um kit de ferramentas para construção de compiladores para linguagens embutidas, detalhe que não precisa nem compilar para usar!


C++ Type Casting – 2ª Parte

Setembro 18, 2008

No post anterior vimos como funciona o casting que o C++ herdou da linguagem C, agora vamos ver como funcionam os novos operadores de casting do C++

static_cast

O static_cast é o mais simples de todos, ele faz em partes o trabalho do cast do C, mas com algumas restrições que veremos na sequência. Baseando-se no exemplo do post anterior onde tínhamos as classes Objeto, Monstro e Tiro pode-se construir:

void CriaExplosao(int potencia);
void ExplodeTiro(Objeto *obj)
{
Tiro *tiro = static_cast<Tiro *>(obj);

CriaExplosao(tiro->Potencia());
}

Por alguma obra do destino que não vem ao caso (ou por falta de criatividade do autor com exemplos) a função ExplodeTiro recebe como parâmetro uma referência para Objeto, e não tiro. Por se tratar da função ExplodeTiro sabemos e confiamos friamente que os programadores sempre vão usar ela passando como parâmetro uma classe Tiro.

O static_cast não faz verificação nenhuma para checar se o Objeto passado como parâmetro é da classe Tiro ou não, apenas ajusta algum endereço se necessário, nada mais. Agora vamos imaginar que um novo programador começou a trabalhar no projeto e sem saber das consequências escreveu:

void func()
{
Monstro m;

ExplodeTiro(&m);
}

O que vai acontecer? Se da ultima vez o cachorro da vizinha latiu, dessa vez é provável que ele exploda, quem sabe o cãozinho que faleceu segundo relataram alguns leitores não ressuscite com essa técnica? Piadas a parte, o comportamento é totalmente imprevisível nesse caso.

A vantagem de se usar o static_cast é que o compilador faz algumas verificações antes de usá-lo:

Tiro tiro;
Monstro *m = static_cast<Monstro *>(&tiro);

O código acima vai dar erros, porque Tiro e Monstro são classes que não podem ser convertidas entre si. Se fosse utilizado um cast do C o compilador aceitaria tudo sem problemas.

Outra vantagem do static_cast é que ele somente permite seu uso com tipos definidos:

class X;
class Y;
void proc(X *x)
{
Y *p = static_cast<Y *>(x);
}

Esse é o mesmo exemplo do artigo do Bjarne listado nas referências abaixo, o interessante desse código é que ele não compila, pois o compilador ainda não conhece a estrutura das classes X e Y, evitando assim a geração de código que pode fazer alguem perder a noite com um bug misterioso.

O static_cast também evita erros como remoção de const por acidente, acesso a membros privados, etc. Dessa forma ele impõe uma série de restrições ao código, minimizando erros que poderiam passar despercebidos.

dynamic_cast

Este cast é utilizado quando é preciso fazer um cast de uma classe base para uma classe derivada. No caso da função ExplodeTiro acima, podemos melhora-la trocando o static_cast pelo dynamic_cast:

void ExplodeTiro(Objeto &obj)
{
Tiro *tiro = dynamic_cast<Tiro *>(&obj);
if(tiro == NULL)
return;

CriaExplosao(tiro->Potencia());
}

Note que caso a conversão não seja possível, o dynamic_cast retorna NULL, dando oportunidade ao programador de verificar se o tipo é valido ou não. No caso da função acima, decidimos simplesmente parar a execução da função quando não é possível fazer a conversão.

O dynamic_cast é o cast mais complexo em termos computacionais do C++, pois ele precisa realizar algumas buscas pela hierarquia de objetos e isso algumas vezes pode levar um tempo precioso, sendo assim, deve-se usá-lo com moderação.

Agora no dia a dia, se o programador tiver certeza absoluta de que o tipo a fazer cast é da classe derivada, não existe problema algum em usar o static_cast ao invés do dynamic_cast (a não ser o risco de fazer coisa errada). Existem códigos onde ao invés dos casts, existe uma template onde na versão debug do código é usado um dynamic_cast com assert, na versão release é usado apenas um static_cast.

Outro detalhe do dynamic_cast é que para ele funcionar é necessário ligar a geração de RTTI (Run Time Type Information – Informação de Tipos em Tempo de Execução), que é opcional em alguns compiladores (Visual Studio por exemplo). Pode ser que algum compilador esse item não seja opcional, mas para o dynamic_cast funcionar ele tem que existir. No caso do visual, se alguem tenta usar o dynamic_cast sem RTTI ele vai gera um erro em tempo de compilação.

reinterpret_cast

Este cast é utilizado quando queremos converter um tipo para um outro tipo não relacionado, como por exemplo de char* para int*. Ele é apenas uma indicação ao compilador de que você sabe o que esta fazendo. Note que ele também pode ser usado para tipos que ainda não foram definidos, mas atenção, o reinterpret_cast diferentemente do dynamic_cast não navega pela hierarquia de classes. Exemplos:

void ExplodeTiro(Objeto *obj)
{
Tiro *tiro = reinterpret_cast<Tiro *>(obj);

CriaExplosao(tiro->Potencia());
}

No exemplo acima o código compila, mas a não ser que a intenção seja realmente essa, esse código somente vai dar problemas. No primeiro cast da classe Objeto para Tiro quando o reinterpret_cast é usado os ponteiros não são ajustados. O ajuste de ponteiros é feito quando trocamos ponteiros de uma classe filha para uma classe pai ou vice versa, isso depende da implementação, mas baseado no exemplo, o ponteiro para Tiro pode não possuir exatamente o mesmo endereço do ponteiro para Objeto (mesmo se tratando do mesmo objeto). Geralmente em hierarquias simples (sem herança múltipla ou herança virtual) o endereço é sempre o mesmo.

O reinterpret_cast é recomendado apenas para operações onde se deseja converter um tipo básico para ponteiro e vice-versa:

void func()
{
int i = 5;
char *p = reinterpret_cast<char *>(i);

++p;

i = reinterpret_cast<int>(p);
}

Outro detalhe é que o reinterpret_cast também preserva constness de tipos assim como o static_cast.

const_cast

O ultimo tipo e o mais simples de todos é o const_cast, este cast simplesmente tira o const de um tipo:

void ExplodeTiro(const Objeto *obj)
{
Tiro *tiro = const_cast<Tiro *>(static_cast<const Tiro *>(obj));

CriaExplosao(tiro->Potencia());

//um cast tipico apenas para remover o cont
Objeto *o = const_cast<Objeto *>(obj);
}

Note que foi usado um static_cast que converte para “const Tiro *”, pois se fosse usado dentro do static_cast apenas “Tiro *” o compilador iria reclamar, então com o resultado dele é aplicado o const_cast, que remove o const do tipo. As pessoas reclamam que é muito código para pouca coisa, mas em troca disso temos um código mais seguro, além de ficar bem explicito quais são as intenções do programador e para terminar, essas operações não implicam custos extras em relação a um cast tradicional do C, então o uníco motivo para não usar é preguiça na hora de digitar.

Sobre o exemplo acima, o ideal é mudar o método potência e transforma-lo em um método const, mas vamos supor que isso não fosse possível por alguma outra obra do acaso :) .

Conclusões

Os casts do C++ trazem mais segurança ao programador e ajudam a evitar erros de conversões invalidas. Apesar de eles serem muito mais longos que um cast normal (em termos de tamanho de código a se digitar) é vantajoso utiliza-los em vista das vantagens que eles trazem.

Caso esteja em duvida sobre qual cast utilizar, utilize o static_cast, se o compilador reclamar:

  • Se for um const sendo removido, estude o caso e veja se é realmente necessário, caso sim, utilize o const_cast.
  • Se for algum tipo incompleto, adicione o include apropriado para definir o tipo ou veja se é necessário trocar por um reinterpret_cast.

Referência

New Casts Revisited – Bjarne Stroustrup: a base de todo esse post, contém vários exemplos de como utilizar e de como bagunçar tudo com os casts do C++.