Linguagem de programação
Uma linguagem de programação é um método padronizado para
comunicar instruções para um computador.1 É um conjunto de regras
sintácticas e semânticas usadas para definir um programa de computador.2 Nota
1 Permite que um programador especifique precisamente sobre quais dados
um computador vai actuar, como estes dados serão armazenados ou
transmitidos e quais acções devem ser tomadas sob várias circunstâncias.
Linguagens de programação podem ser usadas para expressar algoritmos com
precisão.
O conjunto de palavras (lexemas classificados em tokens), compostos de
acordo com essas regras, constituem o código fonte de um software.3 Esse
código fonte é depois traduzido para código de máquina, que é executado
pelo processador.3
Existem dois tipos de linguagens de programação: as de baixo
nível e as de alto nível. Os computadores interpretam tudo como
números em base binária, ou seja, só entendem zero e um. As
linguagens de baixo nível são interpretadas directamente pelo
computador, tendo um resultado rápido, porém é muito difícil e
incômodo se trabalhar com elas. Exemplos de linguagens de baixo
nível são a linguagem binária e a linguagem Assembly.
Exemplo de código em Assembly:
MOV r0, #0C ;load base address of string into r0
LOAD: MOV r1,(r0) ;load contents into r1
CALL PRINT ; call a print routine to print the character in r1
INC r0 ;point to next character
JMP LOAD ;load next character
Quando programamos em uma linguagem de programação de alto
nível primeiramente criamos um arquivo de texto comum contendo a
lógica do programa, ou seja, é onde falamos ao computador como
deve ser feito o que queremos. Este arquivo de texto é chamado
de código-fonte, cada palavra de ordem dentro do código-fonte é
chamada de instrução. Após criarmos o código-fonte devemos
traduzir este arquivo para linguagem binária usando o compilador
correspondente com a linguagem na qual estamos programando. O
compilador irá gerar um segundo arquivo que chamamos de
executável ou programa, este arquivo gerado é interpretado
directamente pelo computador.
COMPILADORES
Um compilador é um programa de sistema que traduz um programa
descrito em uma linguagem de alto nível para um programa
equivalente em código de máquina para um processador. Em geral,
um compilador não produz directamente o código de máquina mas
sim um programa em linguagem simbólica (assembly)
semanticamente equivalente ao programa em linguagem de alto
nível. O programa em linguagem simbólica é então traduzido para o
programa em linguagem de máquina através de montadores.
O compilador é um dos dois tipos mais gerais de tradutores,
juntamente com o interpretador.
Para apresentar um exemplo das actividades que um compilador
deve desempenhar, considere o seguinte trecho de um programa em
C:
Para o compilador, este segmento nada mais é do que uma sequência
de caracteres em um arquivo texto. O primeiro passo da análise é
reconhecer que agrupamentos de caracteres têm significado para o
programa -- por exemplo, saber que int é uma palavra-chave da
linguagem e que a e b serão elementos individuais neste programa.
Posteriormente, o compilador deve reconhecer que a sequência int a
corresponde a uma declaração de uma variável inteira cujo
identificador recebeu o nome a.
As regras de formação de elementos e frases válidas de uma
linguagem são expressos na gramática da linguagem. O processo de
reconhecer os comandos de uma gramática é conhecido como
reconhecimento de sentenças. Gramáticas e reconhecimento de
sentenças serão estudados na Seção 3.1.
A aplicação do conceito de reconhecimento de sentenças para
agrupar as sequências de caracteres em ``palavras'' é a análise
léxica. Os elementos reconhecidos nessa primeira etapa da
compilação são denominados itens léxicos ou tokens. Para o exemplo
acima, a seguinte lista de tokens seria reconhecida pelo compilador:
Para desempenhar a análise léxica, o compilador deve ter
conhecimento de quais são os tokens válidos da linguagem, assim
como suas palavras-chaves e regras para formação de
identificadores. Por exemplo, a declaração
int 1var;
deve resultar em erro nessa etapa da análise, pois 1var não é uma
constante ou identificador válido. Se um programa contendo esta
declaração fosse compilado usando o compilador gcc, por exemplo, a
esta linha seria associado o erro ``nondigits in number and not
hexadecimal'', mostrando que o compilador esperava que 1var fosse
um número, já que inicia com um algarismo. A análise léxica será
estudada na Seção 3.2.
O segundo passo da análise desempenhado pelo compilador é a
análise sintática, onde a estrutura do programa é analisada a partir
do agrupamento de tokens. Nesta etapa, que será estudada na Seção
3.4, o compilador deverá reconhecer que a seqüência de tokens
obtida do segmento de código apresentado no início da seção
corresponde a quatro comandos, sendo o primeiro deles um comando
de declaração de variáveis e os três restantes de atribuição.
Adicionalmente, deverá reconhecer que o último dos comandos de
atribuição contém subexpressões que deverão ser avaliadas para
completar a atribuição na execução do programa.
Para que este passo possa ser realizado pelo compilador, ele deve ter
conhecimento de como são formados os comandos válidos da
linguagem. Por exemplo, um comando de declaração de variáveis
como
int int x;
deve resultar em erro nesta etapa -- o compilador gcc indicaria o erro
com uma mensagem ``two or more data types in declaration of `x'
''.
Após realizada a análise, na fase de síntese o compilador deverá
gerar o código em linguagem simbólica equivalente ao código
analisado. Por exemplo, para um compilador C gerando código para a
linguagem simbólica do processador 68000 os seguintes
mapeamentos poderiam estar preparados:
onde L e R seriam substituídos pelo compilador pelas variáveis
usadas internamente na síntese do programa.
Na verdade, compiladores não trabalham diretamente com o código
de um processador específico. Normalmente o código gerado nessa
fase é expresso em alguma linguagem intermediária, próxima do
assembly mas independente de processador, que depois pode ser
mapeada para diversos processadores distintos.
Este é um código correto sob o ponto de vista de que as tarefas
executadas correspondem semanticamente ao programa C original.
Entretanto, uma simples observação demonstra que o código gerado
poderia ser muito mais sucinto através da eliminação de alguns
comandos desnecessários
A melhoria do código gerado pelo compilador é a fase final da
compilação, que é a otimização de código.
Em programas mais complexos, há muitas outras situações onde o
código gerado automaticamente pelo compilador pode ser melhorado.
O objetivo da fase de otimização é detectar tais situações
automaticamente e aplicar estratégias heurísticas para permitir a
melhoria desse código.
DEPURADOR
Um depurador (em inglês: debugger) é um programa de computador
usado para testar outros programas e fazer sua depuração, que
consiste em encontrar os defeitosdo programa.
O código a ser examinado pode estar sendo executado sob uma
máquina virtual, uma técnica que permite total flexibilidade de acesso
aos estados da máquina virtual, que também é software. Mas os
processadores modernos têm muitos recursos, que também facilitam
o acesso a instruções do programa. Por exemplo, desde há muito
tempo é possível interromper a execução de instruções do programa
depurado em qualquer ponto e examinar o conteúdo de suas
variáveis.
Quando o programa aborta, o depurador mostra a posição no código
fonte original, se for um depurador de código fonte, ou um depurador
simbólico. Caso seja um depurador de linguagem de máquina, ele
mostra a linha onde ocorreu o problema através de desmontagem.
Um programa é abortado quando ele usa uma instrução não
disponível (inválida) na CPU, quando tenta acessar memória
inexistente ou não disponível para ele, bloqueada pelo mecanismo de
proteção. Ou seja: quando tenta acessar, devido a defeito ou não,
recursos indisponíveis para ele. Em geral, o sistema operacional é
que faz com que o programa aborte.
Em geral, os depuradores também oferecem funcionalidades mais
sofisticadas, como: a execução passo-a-passo de um programa; a
suspensão do programa para examinar seu estado atual, em pontos
predefinidos, chamados pontos de parada; o acompanhamento do
valor de variáveis, que podem ser usadas inclusive para gerar uma
suspensão, ou ativar um ponto de parada.
Apesar da importância do uso de depuradores, é bom lembrar que
um programa pode se comportar de forma diferente quando
executando sob um depurador. O bom humor leva este problema a
ser chamado deHeisenbug na literatura especializada, em alusão à
incerteza de Heisenberg. Por exemplo, um depurador alterará a
velocidade de processamento do programa, o que tanto fará surgir
novos problemas desincronização, quanto poderá mascarar aqueles já
existentes. Por esta razão, mesmo os melhores depuradores
existentes podem não ser eficazes na solução dos problemas de
sincronização muitas vezes presentes em sistemas multitarefa ou
distribuídos.
É interessante notar que as mesmas funcionalidades que tornam um
depurador útil para o desenvolvedor que precisa eliminar defeitos,
podem ser usadas por quem queira quebrar proteção contra cópia, ou
fazer um crack de software com alguma limitação de uso.
Muitos programadores—em particular aqueles habituados ao
desenvolvimento em ambientes de desenvolvimento integrado, não
gostam ou têm dificuldade de trabalhar com depuradores com
recursos limitados de interacção, como é o caso de depuradores que
oferecem apenas uma interface de linha de comando. Em vez disso,
preferem usar front-ends que acrescentem funções de visualização e
interacção mais sofisticadas e flexíveis, principalmente através de
interface gráfica com o usuário.
Cada linguagem de programação foi criada com algum objetivo,
como por exemplo, facilidade de escrita, facilidade de manutenção,
melhora da performance do dispositivo, etc.
Quando falamos em níveis, podemos dizer que uma linguagem de
alto nível está muito mais próxima do programador do que do
dispositivo, ou seja, é uma linguagem muito mais intuitiva. Existem
linguagens onde é feito um diagrama e esse diagrama que nada mais
é do que um desenho é convertido para uma linguagem de
programação pré-seleccionada. Essa é uma linguagem bem mais
amigável ao programador devido à sua facilidade de entendimento.
Um exemplo de linguagem de alto nível é a linguagem SDL
(Specification Design Language).