Mês: fevereiro 2009

Tipos Escalares e Tipos Complexos ou Tipos por Valor e Referência

Visão Geral

O objetivo deste artigo é explicar em detalhes o funcionamento de ponteiros e referências em linguagens de programação. Foi usado o termo “ponteiros e referências” justamente para relacionar o comportamento de uma referência para a instância de uma classe com um ponteiro em linguagens de nível mais baixo. O comportamento é essencialmente o mesmo.

É muito importante entender e esclarecer esse conceito, pois o mesmo é **base para o entendimento de orientação a objetos**. Conhecendo esse conceito, dá pra entender como resolver problemas complexos do dia-a-dia usando classes abstratas ou interfaces em OOP (Object Oriented Programming ou Programação Orientada a Objetos).

Tipos por “Valor” e por “Referência”

Esse conceito é muito importante. Quando declaramos alguma variável, dependendo do tipo de dados que estamos declarando essa variável a mesma pode conter diretamente o valor ou simplesmente uma referência para um lugar na memória onde está esse valor.

Exemplos de tipos de dados por valor:

  • Int
  • Double
  • Char

Exemplos de tipos de dados por referência:

  • Classes
  • Interfaces

Na prática, quando trabalha-se com estruturas grandes (como classes por exemplo), que contém uma série de propriedades e consequentemente um espaço maior na memória, é muito mais inteligente e econômico trabalhar com referências.

Quando deseja-se trabalhar com estruturas pequenas (um único Int32, 32 bits, uma string que pode não ser lá muito pequena, um float, um DateTime), é muito mais simples alocar todo o espaço necessário do que criar uma referência para ele.

O caso específico de string é um pouco mais complexo, pois é um tipo que para uso corrente é um tipo por valor, mas internamente é um tipo por referência. Vamos fingir que esse caso não existe e que string é um tipo por valor. Facilita a compreensão pensar assim. Quem quiser complicar a cabeça é só pensar que uma string também pode ser uma [http://pt.wikipedia.org/wiki/Lista_encadeada lista encadeada]. Na maioria dos compiladores, internamente são tratadas como arrays de char.

O que muda na prática?

Sempre que uma variável por valor é declarada, automaticamente existe um espaço de memória para ela.

Sabemos, pela definição do tipo que os tipos ocupam o seguinte espaço na memória:

  • Int: 4 bytes.
  • Double: 4 bytes
  • Char: 1 byte
  • Qualquer tipo por referência: 8 bytes (provavelmente 16 bytes em arquitetura 64 bits. Não tenho certeza.)

Exemplos de tipo por valor

private void teste(){
  int a = 10; //Nesse momento, criei um espaço de 4 bytes na memória, que cabe um inteiro.
  int b = 20; // Nesse momento, criei outro espaço de 4 bytes na memória que cabe um inteiro.
}

Exemplos de tipos por referência

Definição de classe exemplo

A classe de exemplo a seguir usa 2 ints e um float, ou seja, ou seja, cada vez que essa classe é instanciada, ela ocupa 12 bytes em memória. Justamente por classes serem estruturas mais complexas, elas não são instanciadas sempre que uma variável é definida (conceito de tipo por referência, o oposto do tipo por valor).

public class MinhaClasse{
  private int _testeInt;
  public int testeInt{
    get { return _testeInt; }
    set { _testeInt = value; }
  }

  private int _testeInt2;
  public int testeInt2{
    get { return _testeInt2; }
    set { _testeInt2 = value; }
  }

  private double _testeFloat;
  public double testeFloat{
    get { return _testeFloat;}
    set { _testeFloat;}
  } 
}

Exemplo de Alocação de Referências

Nesse caso, para ilustrar o exemplo, sabemos que “MinhaClasse” ocupa 12 bytes na memória, pq define 2 ints e um float.

Então, exemplificando temos o seguinte comportamento:

private void teste(){
  int a = 10; //Nesse momento, criei um espaço de 4 bytes na memória, que cabe um inteiro.
  int b = 20; // Nesse momento, criei outro espaço de 4 bytes na memória que cabe um inteiro.
  /* Nesse momento, criei outro espaço de 4 bytes na memória que cabe uma referência 
     para uma instância de MinhaClasse, e por default recebe valor null */
  MinhaClasse c; 
  /* Nesse momento, criei outro espaço de 4 bytes na memória que cabe uma referência 
     para uma instância de MinhaClasse, e por default recebe valor null */
  MinhaClasse d;
  /* Nesse momento, criei outro espaço de 12 bytes. Esse sim tem espaço para guardar os valores
     na memória referente às propriedades da instância da classe. */
  d = new MinhaClasse();
  /* Nessa última instrução, eu não criei mais nenhum espaço na memória. Somente apontei a mesma instância que estava em d
     para c. Uso os mesmos 4bytes alocados anteriormente para isso. */
  c = d;
}

Tentando ser mais didático

Para tentar criar uma analogia que simplifique o entendimento do problema, vamos pensar numa situação do nosso mundo real.

Vamos imaginar um computador numa rede TCP/IP. Como sabemos, quando temos a nossa máquina na rede, ela pode ser encontrada de muitas formas. Inúmeras entradas de DNS e IP’s podem apontar para uma mesma máquina física.

Todos os 4 lugares apontam para uma mesma máquina física.

Vamos pensar num tipo hipotético chamado “Computador” e vamos pensar que esse tipo hipotético é “por valor”.

  Computador a;
  Computador b;

Executando esse código, eu criei DOIS COMPUTADORES. Temos nesse caso a imagem de um computador repousando sobre uma mesa.

Vamos apagar o exemplo anterior da nossa cabeça e pensar que este mesmo tipo Computador agora é por referência.

  Computador a;
  Computador b;

Executando o código acima, eu criei ZERO computadores. A imagem aqui é uma mesa vazia, sem nenhum computador repousando sobre ela. O que eu criei nesse caso foi uma entrada “a” e outra entrada “b” no servidor DNS. Ambas não apontam para lugar nenhum (null reference).

  Computador a = new Computador();
  Computador b = a;

Nesse caso, quando eu dei o “new”, aí sim. Existe um computador repousando sobre a mesa, e a entrada de DNS “a” aponta para esse novo computador. Quando eu declaro Computador b = a, eu estou criando uma nova entrada de DNS, apontando para o mesmo computador.

  Computador a = new Computador();
  Computador b = new Computador();

Nesse outro exemplo, eu tenho dois computadores repousando sobre a mesa.

Em outras palavras:
Quando uma variável de um tipo por referência é declarada, essa variável aponta para um endereço na memória onde existe uma instância daquele tipo (instância seria o computador, a variável a entrada no servidor DNS). Quando é dado um “new” num tipo por referência, ele cria um espaço na memória onde cabe a instância de uma estrutura e armazena o endereço onde está a estrutura na variável indicada.

Alocação de Memória – Heap x Stack (Pilha)

Recentemente, com a colaboração do Joel Pereira, consegui aprender mais um pouquinho sobre o assunto.

Geralmente os desenvolvedores que começam com linguagens de programação como o C#, de um nível um pouquinho mais alto (talvez eu me inclua nesse caso, apesar de não ter começado com C#), acabam pecando por desconhecer um pouco detalhes de como funciona a alocação de memória num nível mais baixo.

Dentre as áreas de memória dos executáveis, temos como principais as áreas do Heap e do Stack. O Stack é um espaço de memória que nosso programa usa para armazenar os valores das variáveis dentro de um método (variáveis locais). Cada chamada que é empilhada no stack ganha um espaço exclusivo dentro dele, e este é liberado no término da execução do método.

O heap representa a área compartilhada da memória, para todo o programa. Aqui vão todas as variáveis, instâncias de objetos e tudo o mais que precise ser compartilhado em todo o processo.

Sempre que instanciamos uma classe (ex.: new Computador()), alocamos no heap o espaço necessário para as informações do computador, e numa variável local (no stack) o endereço de memória que aponta para essa instância no heap.

Por isso que em linguagens de baixo nível (que não tem um Garbage Collector), acabam ocorrendo problemas de “vazamento de memória” (memory leak). Perde-se o ponteiro para a instância no heap e o espaço ficará lá em uso até que o processo termine.

No caso, em ambientes como o .Net em que existe um garbage collector, esse é o trabalho dele. Procurar e liberar o espaço usado por essas instâncias perdidas no heap que não podem mais serem recuperadas porque não existem mais ninguém apontando pra ela. O problema é que gerenciar a memória do Heap não é uma tarefa muito fácil, por causa dos “buracos” que podem ficar caso deixemos de usar um objeto que está entre dois outros que ainda precisam viver na memória. Já no stack, não temos esse problema, pois a memória está sempre no formato de pilha, simplificando o processo de alocação e liberação da memória. Em outras palavras: gerenciar memória do Stack é mais “barato” que gerenciar memória do Heap.

Em resumo, as variáveis de tipos escalares acabam sempre sendo armazenadas na pilha e as instâncias no heap.

Um outro link interessante sobre o assunto (exemplos em C): http://www.inf.ufsc.br/~ine5384-hp/Estruturas.AlocDinamica.html, de autoria do Prof. Dr. rer.nat. Aldo von Wangenheim.

Outro post interessantíssimo é do Eric Lippert. Ele fala um pouco mais sobre o assunto focado em C# e joga um pouquinho mais de lenha na fogueira, afirmando que nem sempre variáveis locais são armazenadas no stack. Segue o link: http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx.

Anúncios

Subversion (TortoiseSVN) Tutorial

O que é o Subversion?

Subversion (SVN) é um software de controle de versões. Tem por objetivo permitir rastrear todas as alterações ocorridas nos arquivos armazenados por ele.

O cliente que estaremos usando para o Subversion é o TortoiseSVN, muito similar ao TortoiseCVS. O TortoiseSVN é open-source e pode ser obtido em http://www.tortoisesvn.org.

Este tutorial tem foco no cliente subversion. Se deseja mais informações sobre como instalar o “servidor”, por favor dê uma olhada nesse tutorial: Tutorial de Instalação do Subversion + SVN::Notify em Win32.

Gostaria muito de receber feedback sobre este tutorial: O que está fácil de entender, o que está difícil e qual outro tópico poderia ser melhor detalhado. Fiquem à vontade para inserir comentários (rodapé da página).

Operações do Subversion

Checkout

O conceito de checkout do SVN é exatamente o mesmo que o do CVS, porém para usuários do SourceSafe o conceito é diferente.

No SubVersion o Checkout significa a criação da cópia de trabalho (working copy) do repositório na máquina do desenvolvedor ou ainda “baixar” o código fonte do repositório.

Para realizarmos um checkout no nosso repositório, clicamos com o botão direito no Windows Explorer em cima do diretório onde o repositório será baixado, por exemplo C:\ME\SandBoxes\MEWeb e depois em SVN Checkout.

A URL do repositório é fornecida pelo administrador do repositório. Observe atentamente a diferenciação de maiúscula/minúscula no nome do repositório. Deve ser exatamente igual.

Em revision, mantenha “HEAD” revision. O outro caso é caso uma revisão específica seja desejada.

Clique em Ok, o TortoiseSVN irá pedir a autenticação do servidor. O usuário/senha deve ser fornecido pelo administrador do repositório. É recomendável ligar o “Save Authentication”, para que ele não fique solicitando a autenticação toda hora.

Se por alguma razão, desejar apagar as informações de autenticação, clique com o botão direito no Windows Explorer, depois TortoiseSVN, Settings e na tab General, clique em “Clear Now” à frente de “Subversion authentication data”.

Após finalizar o checkout, você observará um ícone verde exibido sobre o diretório. Isso indica que desde o último checkout ou update não houveram alterações no repositório.

Alterando arquivos

Os arquivos baixados pelo TortoiseSVN no diretório local ficam “alteráveis” (no Visual Source Safe ou em outros softwares de controle de versão geralmente eles ficavam Read Only).

Você pode alterar à vontade os arquivos dentro da sua Working Copy.

Nenhuma alteração vai para o repositório enquanto essas alterações acontecem.

Sempre que um arquivo é alterado, o TortoiseSVN visualmente indica em quais arquivos e diretórios recursivamente (do arquivo até o diretório principal do repositório) houveram alterações, com o ícone vermelho.

Commit

A operação de commit é a mais importante do SVN. O conceito é exatamente o mesmo em relação ao CVS. Já no SourceSafe a operação equivalente ao commit é o CheckIn.

Ela consiste em enviar as alterações realizadas na sua “Working Copy” (onde você baixou os fontes) para o repositório no servidor.

Toda e qualquer operação como alteração de arquivo, Add, Delete, Rename, Ignore não acontecem até que seja realizado um commit no repositório.

Assim como as demais operações do TortoiseSVN, elas podem ser executadas no diretório ou no arquivo. Caso seja executada no diretório, todos os arquivos e subdiretórios farão parte da mesma operação.

Quando é realizada a operação de commit, a seguinte tela é exibida:

Clicando-se em “Recent messages” pode-se obter mensagens anteriormente usadas em commits.

Na caixa de texto maior, abaixo de recent messages, devem ser adicionados comentários sobre as alterações realizadas no repositório. É muito importante que essas informações sejam inseridas. Através delas será possível rastrear as alterações.

Em changes made, são exibidos todos os arquivos que estão diferentes do repositório (inclusive arquivos não versionados, recentemente adicionados, excluídos, propriedades de diretórios modificadas, etc.).

Clicando em OK, as alterações são enviadas para o servidor.

Se entre o momento em que suas alterações foram realizadas outro usuário alterou algum dos arquivos e subiu as alterações para o repositório (commit), vc receberá a mensagem:

Nesse caso, realize um update na sua working copy e na seqüência realize um novo commit.

Update

O Update do TortoiseSVN significa pegar as alterações mais atuais do repositório e atualizar a Working Copy. A grande diferença é
que o TortoiseSVN nunca sobrescreve as suas alterações quando um update é realizado. No momento do update, o subversion detecta se houve um commit anterior no mesmo arquivo e faz um “merge” das alterações.

Para se realizar o update, clica-se com o botão direito sobre o arquivo ou diretório, depois em SVN Update. Caso seja selecionado um diretório, a operação é realizada recursivamente em todos os subdiretórios e arquivos.

Update normal, sem merge automático ou conflito

Caso não existam alterações na cópia de trabalho, ou as alterações realizadas na cópia de trabalho não sejam em arquivos que tiveram atualizações de outros usuários, o TortoiseSVN automaticamente os atualiza. Ex.:

Update com Merge

Caso existam alterações na cópia de trabalho, e também existirem atualizações nos mesmos arquivos alterados, o TortoiseSVN pode tomar duas ações:

Merge automático

Quando o TortoiseSVN detecta que os arquivos foram alterados em linhas diferentes. Ex.:

Conflito

Quando dois arquivos foram alterados exatamente no mesmo lugar. Essa é uma situação relativamente rara, porém pode acontecer na prática.

Quando uma situação de conflito acontece, o TortoiseSVN exibe o arquivo em conflito com o ícone amarelo e automaticamente tira backups dos arquivos, com as extensões:

  • teste.txt.mine: A versão da working copy, sem qualquer alteração
  • teste.txt.r6: A versão da revisão 6, anterior ao update
  • teste.txt.r7: A versão da revisão 7, revisão posterior ao update.

No caso de conflito deve-se clicar com o botão direito e “Resolve conflicts”. Isso dispara automaticamente a ferramenta de Merge. É possível usar a ferramenta de Merge default do TortoiseSVN ou alguma outra externa. Isso pode modificar a forma de se resolver os conflitos.

Na ferramenta TortoiseMerge, as alterações são automaticamente salvas no arquivo alterado (ex.: teste.txt). Na ferramenta Araxis Merge, ao editar os conflitos, o arquivo editado é sempre o .mine (ex.: teste.txt.mine), ou seja na hora de “resolver” o conflito, deve-se usar a opção “Resolve conflicts using .mine”.

É recomendável conversar com o desenvolvedor que realizou a alteração anterior caso existam dúvidas sobre a lógica de programação envolvida no código.

Após a resolução do conflito, nenhuma versão é subida para o servidor. O arquivo fica como “alterado” na working copy. Caso deseje enviar as alterações para o servidor, deve-se realizar um “Commit”.

Show Log

Exibe o histórico de alterações do item selecionado. No caso do Subversion, é possível verificar alterações em propriedades dos arquivos e diretórios.

Diff

Diffs servem para comparar arquivos. Um comparador externo (Araxis Merge) pode ser configurado para realizar as comparações. Sempre que um arquivo está alterado no repositório e um diff é realizado, as alterações feitas são exibidas.

É possível realizar a comparação entre quaisquer versões dos arquivos, através do “Show Log”.

Adicionando Arquivos ou Diretórios

Sempre que um novo arquivo é jogado na Working Copy, por default ele fica como não versionado. Para fazer o TortoiseSVN “reconhecer” um arquivo como adicionado, deve-se clicar com o botão direito nele depois em TortoiseSVN | Add.

Quando o Add é realizado, o arquivo ainda não foi efetivamente para o repositório. Somente no próximo commit ele será enviado.

Removendo Arquivos ou Diretórios

Sempre que um arquivo é excluído da Working Copy, ele não foi removido do repositório. No próximo update, ele será novamente baixado do repositório.

Se desejar excluir efetivamente um arquivo ou diretório, deve-se clicar com o botão direito sobre ele, depois em TortoiseSVN depois em Delete.

Mesmo o arquivo “sumindo” da working copy, ele não foi efetivamente removido do repositório. Somente no próximo commit ele será efetivamente removido. Consultando em revisões anteriores (show log), o arquivo ainda existirá e poderá ser recuperado.

Ignorando Arquivos ou Diretórios

Alguns arquivos criados pela IDE as vezes servem apenas para controlar informações na máquina local, e não devem ser versionados.
Nessa categoria temos: .appdata, .suo, .pdb, entre outros. Binários que podem ser gerados através de fontes também não são bem-vindos, assim como código “gerado” a partir de outros artefatos.

Para que o TortoiseSVN não fique mais notificando alterações nesses arquivos ou diretórios, basta clicar com o botão direito sobre ele TortoiseSVN | Add to ignore list.

Para a alteração surtir efeito, deve ocorrer um commit no diretório (a informação de ignore é uma propriedade do diretório, diferentemente do .cvsignore do cvs).

Revert – Desprezando suas alterações

Caso você tenha realizado alterações em sua working copy e deseje desprezá-las, podemos usar o comando “Revert”, clicando com o botão direito, TortoiseSVN | Revert.

Como praticamente todos os comandos do TortoiseSVN ele pode ser aplicado somente num arquivo ou num diretório. No caso do diretório, ele irá recursivamente se aplicando em subdiretórios e arquivos.

Alterações significativas de conceitos do CVS para o SVN

  • Commit atômico: No caso do CVS, quando um “lote” de alterações seja commitado, caso ocorra um erro, ele não consegue dar um “rollback” em todo o commit. Os arquivos que foram parcialmente commitados são gravados no repositório. O Subversion não tem esse problema. Ou o commit acontece inteiro ou ele não acontece.
  • Número da revisão: No Subversion, o número da “revisão” é para o repositório todo. Caso um arquivo seja alterado, é gerado uma nova versão para o repositório inteiro. No CVS por sua vez, o número da revisão é do arquivo.

Tutorial de Instalação do Subversion + SVN::Notify em Win32

Insumos necessários

Instalação

Instale o Subversion 1.5.5. Esse instalador da CollabNet automaticamente instala o apache. É importante verificar se já não tem um IIS rodando na porta 80. Nesse caso, mude ou o apache ou o IIS de porta.

Configurando Autenticação SSPI (Integração com Active Directory)

Caso deseje autenticação SSPI, baixe o módulo do apache no endereço http://sourceforge.net/projects/mod-auth-sspi

Descompacte o zip e copie o arquivo mod_auth_sspi.so para o C:\Program Files\CollabNet Subversion Server\httpd\modules.

Edite o arquivo C:\Program Files\CollabNet Subversion Server\httpd\conf\httpd.conf, adicionando a linha a seguir abaixo de toda a seção “LoadModule”:

LoadModule sspi_auth_module modules/mod_auth_sspi.so

No final do arquivo httpd.conf, adicione as seguintes linhas:

<Location /svn>
  SSPIAuth On
  SSPIAuthoritative On
  SSPIDomain <nome do domínio>
  SSPIOfferBasic On
  DAV svn
  SVNListParentPath on
  SVNParentPath C:\SVN_repository
  AuthType SSPI
  #AuthzSVNAccessFile svnaccessfile
  SSPIUsernameCase lower
  #SSPIOmitDomain
  Require valid-user
  </Location>

Criando repositórios

Os repositórios devem ser criados via linha de comando no servidor, ex.:

svnadmin create c:\svn_repository\meurepositorio

Instalando o SVN::Notify

Instale o ActivePerl. As opções default servem bem.

Após a instalação, utilize o Perl Package Manager (ppm) para instalar os seguintes módulos. O SMTP_auth só é necessário caso precise de SMTP autenticado:

ppm install Net::SMTP_auth
ppm install SVN::Notify

Configurando o SVN::Notify para ser disparado quando um commit ocorrer no repositório

Crie um arquivo com o nome c:\svn_repository\meurepositorio\hooks\post-commit.bat

Esse arquivo deve ter um conteúdo similar a este:

set REPOS=%1
set REV=%2
SET PATH=C:\Program Files\CollabNet Subversion Server;C:\PERL\bin;c:\perl\site\bin;
SET OS=Windows_NT
SET SystemRoot=C:\WINDOWS

svnnotify --repos-path %REPOS% -r %REV% --from from@meudominio.com.br --to destino@meudominio.com.br --smtp meusmtp.meudominio.com.br -H HTML::ColorDiff -d --smtp-user meuuser --smtp-pass meupassword --verbose --verbose

Alguns erros comuns, que só são exibidos quando o svnnotify é chamado com –verbose (2 vezes):

  • Cannot fork: Bad file descriptor – Provavelmente tem algo errado na variável PATH (não aponta pro lugar certo dos binários do svn, onde fica o svn.exe no servidor)
  • Unable to create Net::SMTP_auth object – Provavelmente o SystemRoot está errado. O erro não aparece muito claro no output do svnnotify.