Linguagem C Disciplina: AAM Prof. Eduardo Henrique Couto 2014/01 Linguagem C • Objetivo da aula: • Revisão da Linguagem; • Otimização de programa em C para Sistemas Embarcados. • Exercício Revisão da Linguagem C • Surgiu nos anos 70 de uma linguagem chamada B. criada por Dennis Ritchie. • Qualquer programa C ANSI pode ser compilado em qualquer compilador C ANSI não importando a máquina na qual o programa vá ser executado. • Portanto => portabilidade, a escolha acaba recaindo sobre a linguagem C. Revisão da Linguagem C • Esta linguagem foi criada, influenciada e testada em campo por programadores. Ela oferece ao programador exatamente o que ele quer: poucas restrições, código rápido e eficiência. Por isso ela é a linguagem mais popular entre os programadores profissionais altamente qualificados. Revisão da Linguagem C • Programação estruturada: A linguagem C é uma linguagem estruturada em bloco simples => habilidade de uma linguagem tem de seccionar e esconder do resto do programa todas as instruções necessárias para a realização de uma determinada tarefa. Revisão da Linguagem C • Declaração de variáveis: Variáveis devem ser declaradas antes de serem usadas, permitindo assim, que o compilador saiba de antemão • informações como tipo e espaço gasto • em memória podendo fazer checagem • durante o processo de compilação. Revisão da Linguagem C • Funções: Blocos de Código: Conjunto de comandos que executa uma determinada operação no programa. Normalmente é útil quando se necessita repetir um mesmo trecho de código várias vezes em um programa. Revisão da Linguagem C • Sensitive case: Significa que na linguagem C as letras maiúsculas e minúsculas são tratadas como caracteres diferenciados. • Ex.: Podemos declarar uma variável For, apesar de haver uma palavra reservada for, mas isto não é uma coisa recomendável de se fazer pois pode gerar confusão!! Revisão da Linguagem C • Comentários: • /* comentário de uma ou várias linhas. O padrão não permite comentários aninhados (um dentro do outro), mas alguns compiladores os aceitam. */ • // comentário de uma linha!!!! Revisão da Linguagem C Palavras reservadas: auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while Revisão da Linguagem C • ESTRUTURA BÁSICA DE UM PROGRAMA: 1) 2) 3) 4) 5) INCLUSÃO DE ARQUIVOS EXTERNOS DECLARAÇÃO DE VARIÁVEIS GLOBAIS DECLARAÇÃO DE PROTÓTIPOS DE FUNÇÕES FUNÇÃO PRINCIPAL (main) FUNÇÕES Revisão da Linguagem C • Protótipos de Funções: • Normalmente escrevemos as funções antes de escrevermos a função main(). • Isto é, as funções estão fisicamente antes da função main(). • Isto é feito por uma razão. Imagine-se na “pele” do compilador. Se você fosse compilar a função main(), onde são chamadas as funções, você teria que saber com antecedência quais são os tipos de retorno e quais são os parâmetros das funções para que você pudesse gerar o código corretamente. Revisão da Linguagem C • Protótipos de Funções: • Foi por isto que as funções foram colocadas antes da função main(): quando o compilador chegasse à função main() ele já teria compilado as funções e já saberia seus formatos. Revisão da Linguagem C • Protótipos de Funções: • Muitas vezes teremos o nosso programa espalhado por vários arquivos. Ou seja, estaremos chamando funções em um arquivo que serão compiladas em outro arquivo. Como manter a coerência? Revisão da Linguagem C • Protótipos de Funções: • A solução são os protótipos de funções. Protótipos são nada mais, nada menos, que declarações de funções. Isto é, você declara uma função que irá usar. O compilador toma então conhecimento do formato daquela função antes de compilá-la. O código correto será então gerado. Um protótipo tem o seguinte formato: • tipo_de_retorno nome_da_função (declaração_de_parâmetros); Revisão da Linguagem C • tipo_de_retorno nome_da_função (declaração_de_parâmetros); • onde o tipo_de_retorno, o nome_da_função e a declaração_de_parâmetros são os mesmos que você pretende usar quando realmente escrever a função. Repare que os protótipos têm uma nítida semelhança com as declarações de variáveis. Revisão da Linguagem C #include <stdio.h> float Square (float a); int main () { float num; num=Square(num); return 0; } float Square (float a) { return (a*a); } // prototipo Revisão da Linguagem C Tipos de Dados: • char ( sinalizados –positivo ou negativo) ou não sinalizado – positivo) • inteiro ( sinalizados –positivo ou negativo) ou não sinalizado – positivo) • real ( valores inteiros ou fracionários – float ou double) • nulo ( void – tipo vazio, ou um "tipo sem (tipo". Não retornam e/ou recebem valores – parâmetros) Revisão da Linguagem C Modificadores de Tipos de dados: • short (reduz a faixa de representação do tipo) • long (ampliar a faixa de representação do tipo) • signed (para especificar a representação de valores sinalizados – compl. de dois) • unsigned (para especificar a representação de valores não sinalizados) OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS http://sergioprado.org/otimizacao-de-codigo-em-linguagem-c-parte-1 Escrever código para estes dispositivos requer atenção especial quanto a utilização e gerenciamento eficiente dos recursos disponíveis. Por que? OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Sistemas embarcados têm como uma de suas principais características a limitação de recursos computacionais (quantidade de memória, capacidade de processamento e dispositivos de I/O). • Aplicações bem específicas => projetadas na medida para cumprir sua função. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS Vantagens da otimização: • • • • Diminuir custos do projeto; Tempo de desenvolvimento; Tamanho do dispositivo; Consumo elétrico. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS Vantagens da otimização: • • • • Diminuir custos do projeto; Tempo de desenvolvimento; Tamanho do dispositivo; Consumo elétrico. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • O compilador C é a ferramenta que irá transformar seus algoritmos (código-fonte C), na sua aplicação. • O compilador realiza uma série de transformações no código-fonte para gerar o melhor código possível, como por exemplo salvar variáveis em registradores ao invés de usar a memória para melhorar o tempo de acesso (otimização de processamento) ou remover código inútil do programa (otimização de espaço). OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Em um compilador C moderno, o processo de compilação envolve basicamente os seis passos, na ordem apresentada: • 1. Pré-processamento: Conversão do código-fonte em uma linguagem intermediária. É aqui que as diretivas de compilação são processadas e a sintaxe do código é verificada. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • 2. Otimização de alto nível: É nesta etapa que o compilador realiza a primeira otimização do código, em cima do código-fonte da aplicação. Veremos mais adiante algumas das técnicas para auxiliar o compilador neste trabalho de otimização. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • 3. Geração do código: Geração do código de máquina para a arquitetura-alvo. Nesta fase o compilador faz todas as conversões necessárias para a arquitetura em questão. Expressões aritméticas de 32 bits em processadores de 8 bits são convertidas em expressões aritméticas de 8 bits, por exemplo. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • 4. Otimização de baixo nível: Nesta fase a otimização é realizada em cima do código-objeto gerado na fase anterior, como por exemplo removendo instruções não usadas ou redundantes. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • 5. Assembler: Nesta etapa o compilador gera o arquivo-objeto correspondente ao arquivo-fonte C compilado. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • 6. Linkagem: Por último, o compilador faz a linkagem de todos os arquivos-objeto em um único arquivo binário para ser carregado na memória do dispositivo. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Percebe-se que, dentre as seis etapas no processo de compilação descritas, quando pensamos em otimização de código, deve--se focar nas etapas 2 (Otimização de alto nível) e 4 (Otimização de baixo nível). OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS Técnicas que podem auxiliar e instruir o compilador na tarefa de otimizar o código gerado: OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS A) O tempo de acesso aos registradores da CPU é normalmente muito mais rápido do que o tempo de acesso à memória de dados, e o processador possui melhor performance quando realiza cálculos utilizando registradores ao invés de memória. • Porisso, o compilador procura alocar variáveis locais e parâmetros para funções em registradores para melhorar a performance do sistema. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • O compilador precisa decidir quais variáveis irão ser acessadas através de registradores e quais deverão ser mantidas em memória. • Pode-se instruir o compilador através da palavrachave register, conforme mostra a listagem: int exp(register int base, register int expoente) { register int resultado = 1; for (; expoente; expoente--) resultado *= base; return(resultado); } OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • O compilador precisa decidir quais variáveis irão ser acessadas através de registradores e quais deverão ser mantidas em memória. • Pode-se instruir o compilador através da palavrachave register, conforme mostra a listagem: int exp(register int base, register int expoente) { register int resultado = 1; for (; expoente; expoente--) resultado *= base; return(resultado); } OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Devemos ressaltar que nem sempre será possível alocar todas as variáveis em registradores. • A palavra-chave register é apenas uma orientação para auxiliar o compilador a decidir quais variáveis são mais prioritárias para a alocação em registradores. • Por este motivo é importante ter um código modular, com funções pequenas e específicas, para que a quantidade de variáveis locais também seja pequena, aumentando as chances de que estas sejam otimizadas através do armazenamento em registradores. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS B) Funções • Uma chamada de função é um processo complexo e deve ser levado em consideração quando pensamos em performance. Basicamente, antes de uma chamada à função o compilador salva em memória (ou registradores) os argumentos da função e o endereço de retorno. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Essa região de memória é chamada de stack. Dentro da função chamada, os registradores são salvos, parâmetros são lidos do stack ou de registradores, e variáveis locais são alocadas em memória ou em registradores (se disponíveis). • Podemos perceber que, para funções com uma quantidade grande de parâmetros, o custo de uma chamada pode ser enorme para a performance do sistema. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Uma das técnicas para melhorar a performance é diminuir a quantidade de parâmetros passados para funções. • Podemos perceber comparando as duas funções abaixo, que o custo de chamada da função atualiza_dados(), com 5 parâmetros, é muito maior que o custo de chamada da função atualiza_dados_otimizada(), que recebe apenas um ponteiro para uma estrutura de dados como parâmetro. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS void atualiza_dados(char *nome, char *endereco, char *cidade, char *telefone, double salario) { ... } struct Dados { char *nome; char *endereco; char *cidade; char *telefone; double salario; }; void atualiza_dados_otimizada(struct *Dados) { ... } OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS C) Inlining de funções: • A chamada à função é substituída por uma cópia do seu código. Em código C, podemos empregar este recurso através da definição de macros. • No primeiro exemplo, a função soma() será chamada 100 vezes, diminuindo a performance do sistema. No segundo exemplo, usa-se a macro SOMA(), eliminando a chamada à função e conseqüentemente aumentando a performance do sistema. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS #define SOMA(a, b) (a + b) int soma(int a, int b) { return(a + b); } int main() { int i, res, a = 1; /* primeiro exemplo */ for (i = 0; i < 100; i++) res = soma(a, res); /* segundo exemplo */ for (res = 0, i < 100; i++) res = SOMA(a, res); ... } OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS D) Otimização do compilador O compilador pode ser instruído para compilar o programa priorizando tempo de processamento (speed optimization) ou tamanho de código (size optimization). • Como exemplo, vamos acompanhar a tabela, que lista informações sobre a compilação de dois programas distintos: OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • O programa 1 ficou menor quando selecionada a otimização por tamanho de código; • O programa 2 ficou menor quando selecionada a otimização por tempo de processamento! • Isso significa que é sempre bom testar as duas opções no seu código e verificar os resultados, antes de definir a otimização ótima para seu projeto. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS E) Tamanho das variáveis • o compilador é preparado para compilar o código otimizado para a arquitetura-alvo do seu sistema. • Isso significa que se você está trabalhando com um microprocessador de 8 bits, os cálculos terão maior performance se realizados com 8 bits de dados, em variáveis do tipo char. • Da mesma forma, se você estiver trabalhando com processadores de 32 bits, os cálculos terão maior performance se realizados com variáveis do tipo int. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS • Isso acontece porque o compilador realiza os cálculos com base no tamanho de seus registradores, que por sua vez são baseados na arquitetura da CPU. • Se a sua CPU é de 32 bits, seus registradores serão de 32 bits, e se você está realizando cálculos com variáveis char, de 8 bits, nesta arquitetura, o compilador terá o esforço extra de converter o resultado final, de 32 bits, em uma variável de 8 bits. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS F) Incremento de variáveis • É muito mais eficiente incrementar variáveis com o uso de operadores de incremento (ex: ind++) do que o comando de atribuição (ex: ind = ind + 1). • Isso porque a maioria dos compiladores usam , se disponível, a instrução de incremento do processador, o que torna o código-objeto gerado muito mais otimizado. Exemplo: OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS /* incremento com atribuicao */ x = x + 1; /* codigo gerado em assembly mov A, x ; carrega o valor de x no registrador A add A, 1 ; soma 1 ao registrador A mov x, A ; carrega de volta o valor do registrador A em x */ /* operador de incremento */ x++; /* codigo gerado em assembly inc x; incrementa x em 1 */ OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS G) Protótipos para funções • Se o protótipo da função não é devidamente definido, o compilador precisará “adivinhar” quais os tipos de dados com que sua função trabalha, o que diminui bastante a performance do sistema. OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS H) Códigos muito “inteligentes” • Alguns programadores acreditam que, ao escrever em poucas linhas de código fazendo uso “inteligente” de construções complexas em linguagem C, deixarão o código menor e mais rápido. • Cuidado nem sempre o compilador vai criar códigos menores e pode tornar o código quase indecifrável! OTIMIZAÇÃO DE PROGRAMA EM C PARA SISTEMAS EMBARCADOS I) Uso de bibliotecas • Algumas vezes, o uso de apenas uma função de uma biblioteca, como printf() pode trazer junto com ela, implicitamente, outras funções necessárias para sua utilização, e isso pode aumentar bastante o tamanho do seu código. • Quando se trata de sistemas embarcados com poucos recursos disponíveis, às vezes é melhor implementar uma função simples do que utilizar a de uma biblioteca que pode fazer o tamanho do código crescer. Exercício • Completar a tabela dos tipos de dados do JM60 e JM128. • Utilize o CodeWarrior vrs. 10.2 JM60 JM128