capítulo 4 linguagem c: dados

26
Capítulo 4 Linguagem C: Dados Autoras: Daniela de Souza Oliveira e Wu Shin-Ting Os seres humanos se comunicam por meio de um rico sistema de linguagens verbais e não-verbais. Dentre as linguagens verbais temos alfabetos, ideogramas e diferentes sistemas de numeração. O computador, por sua vez, só entende códigos binários porque ele é construído por componentes digitais que operam essencialmente em dois estados (nível lógico 1 e nível lógico 0). A menor unidade de um código binário é o bit binary digit (dígito binário). Ele pode assumir dois valores: 0 ou 1. Ao processo da “tradução” do rico vocabulário de uma linguagem em códigos binários chamamos de representação ou codificação de dados binários. Neste capítulo vamos ver algumas representações de dados mais utilizadas nos sistemas embarcados e suas abstrações em linguagem de alto nível C. Diferentemente da abordagem clássica, o objetivo deste capítulo é mostrar como o compilador gcc-arm do ambiente IDE CodeWarrior [1] traduz tais abstrações em códigos binários. 4.1 Representação de Dados Sob o ponto de vista computacional, os elementos da linguagem verbal são classificados basicamente em quatro categorias: números inteiros sem sinal, números inteiros com sinal, pontos flutuantes e caracteres alfanuméricos [2] . Nesta seção faremos uma breve apresentação das formas de codificação mais usadas. 4.1.1 Números Naturais ou Números Inteiros sem Sinal O sistema de numeração decimal é o mais conhecido entre os seres humanos. Esse sistema é composto por dez diferentes dígitos (0-9). Adota-se a notação posicional para representar um valor, ou seja, o dígito mais à direita em um número é o menos significativo. Cada posição vale 10 vezes mais que a de posição à sua direita. No entanto, o sistema de numeração decimal não é compatível com a natureza binária dos computadores. Para os computadores, o sistema binário que representa os números utilizando

Upload: trannga

Post on 07-Jan-2017

221 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Capítulo 4 Linguagem C: Dados

Capítulo 4

Linguagem C: Dados

Autoras: Daniela de Souza Oliveira e Wu Shin-Ting

Os seres humanos se comunicam por meio de um rico sistema de linguagens verbais e não-verbais.Dentre as linguagens verbais temos alfabetos, ideogramas e diferentes sistemas de numeração. Ocomputador, por sua vez, só entende códigos binários porque ele é construído por componentesdigitais que operam essencialmente em dois estados (nível lógico 1 e nível lógico 0). A menorunidade de um código binário é o bit – binary digit (dígito binário). Ele pode assumir dois valores:0 ou 1. Ao processo da “tradução” do rico vocabulário de uma linguagem em códigos binárioschamamos de representação ou codificação de dados binários.

Neste capítulo vamos ver algumas representações de dados mais utilizadas nos sistemas embarcadose suas abstrações em linguagem de alto nível C. Diferentemente da abordagem clássica, o objetivodeste capítulo é mostrar como o compilador gcc-arm do ambiente IDE CodeWarrior [1] traduz taisabstrações em códigos binários.

4.1 Representação de Dados

Sob o ponto de vista computacional, os elementos da linguagem verbal são classificadosbasicamente em quatro categorias: números inteiros sem sinal, números inteiros com sinal, pontosflutuantes e caracteres alfanuméricos [2]. Nesta seção faremos uma breve apresentação das formasde codificação mais usadas.

4.1.1 Números Naturais ou Números Inteiros sem Sinal

O sistema de numeração decimal é o mais conhecido entre os seres humanos. Esse sistema écomposto por dez diferentes dígitos (0-9). Adota-se a notação posicional para representar um valor,ou seja, o dígito mais à direita em um número é o menos significativo. Cada posição vale 10 vezesmais que a de posição à sua direita.

No entanto, o sistema de numeração decimal não é compatível com a natureza binária doscomputadores. Para os computadores, o sistema binário que representa os números utilizando

Page 2: Capítulo 4 Linguagem C: Dados

apenas dois dígitos (0-1) é o mais apropriado. Este sistema também usa a notação posicional em quecada dígito vale 2 vezes mais que um dígito à sua direita.

Figura 1: Representação de um valor no sistema binário (Fonte: [3])

O sistema de numeração binária é usado, de forma direta, para codificar os números naturais ouos números inteiros sem sinal. Com n bits podemos representar, de forma não ambígua, até 2n

valores (0 até 2n-1). Como a menor unidade endereçável na grande maioria dos computadores é umconjunto de 8 bits, denominado um byte, é comum especificar o tamanho de memória pararepresentar um número inteiro sem sinal em termos de quantidade de bytes. Podemos ter um valornatural representado por 1 byte, 2 bytes, 4 bytes e 8 bytes.

Formas mais compactas para representar códigos binários são agrupamentos dos bits em númerosque são potência de 2, como 4 (sistema quaternário), 8 (octal) e 16 (hexadecimal). Destes sistemas,a compactação mais utilizada em sistemas embarcados é o sistema de numeração hexadecimal.Este sistema tem um fator de multiplicação 16. Ele é composto pelos dígitos de 0-9 e letras A-F, quecorrespondem aos valores em decimal 10-15. Cada dígito hexadecimal é representado por 4 dígitosbinários, o que torna mais fácil a conversão de binário para hexadecimal.

4.1.2 Números Inteiros com Sinal

Números inteiros com sinal incluem tanto os números positivos quanto os negativos. Pararepresentar esses números em binário destacam-se quatro diferentes formas.

A primeira delas é denominada representação de sinal e magnitude, em que um bit, usualmente obit mais significativo, é reservado para representar o sinal do valor. Por convenção, 0 representa osinal “+”, correspondendo a um número positivo, e 1 representa “-”, o que corresponde a umnúmero negativo. Esse bit é chamado de bit de sinal. Cabe ressaltar que o restante da sequência debits, tanto para um número positivo quanto para um negativo, é usado para o código binário domódulo do valor.

A segunda representação é conhecida como complemento de um, em que todos os númerosnegativos são gerados a partir da sua contrapartida positiva, complementando bit a bit todos os seusbits. Ou seja, troca-se os 0’s por 1’s, e vice-versa. Esta representação tem a desvantagem de que hádois códigos binários associados ao número 0. Por exemplo, os dois códigos binários 0000 e 1111

Page 3: Capítulo 4 Linguagem C: Dados

representam o valor zero para um conjunto de números representado por 4 bits.

A terceira alternativa é a representação por complemento de 2, que resolve o problema darepresentação em complemento de 1 e que também facilita a realização de operações aritméticassobre os números inteiros. A notação de complemento 2 de um número negativo é obtida ao somar 1à sua representação em complemento de 1. Com n bits nesta notação podemos representar osvalores de -2n-1 até 2n-1-1. O número 0 possui representação única e o bit mais significativo é o bit desinal. É adotada a mesma convenção das outras duas representações para interpretar este bit: se 0 onúmero é positivo, senão o número é negativo. O interessante é que se quisermos converter umnúmero negativo em complemento de 2 para a sua contrapartida positiva, também em complementode 2, basta complementarmos bit a bit o número e somarmos 1 ao resultado. Um problemaobservado nesta representação é a ordenação dos números pelo valor representado.

A Tabela 1 mostra a representação binária de três valores em sinal-magnitude, em complemento de1 e em complemento de 2.

Tabela 1 - Representação em sinal e magnitude, em complemento de 1 e em complemento de 2.Número em decimal Repres. em binário

(sinal e magnitude)Número de 8 bits emcomplemento de 1

Número de 8 bits emcomplemento de 2

7 0000 0111 0000 0111 0000 0111

35 0010 0011 0010 0011 0010 0011

-28 1001 1100 1110 0011 1110 0100

Ao executarmos o seguinte trecho de declarações de um programa C no ambiente IDECodeWarrior,

uint8_t i1=7, i2=35;signed char i3=-28;

constatamos que são alocados 3 bytes nos endereços 0x20002ff1, 0x20002ff2 e 0x20002ff3 para astrês variáveis. O conteúdo destes endereços é 0xE4, 0x23 e 0x07, respectivamente. Convertendoestes valores em hexadecimal para binário tem-se: 1110 0100 0010 0011 0000 0111. Comparandocom os valores na Tabela 1, verifica-se que eles correspondem aos números -28, 35 e 7 emcomplemento de 2.

Figura 2: Representação em complemento de 2.

A quarta representação é a representação de excesso-N. Esta representação usa o valor N como umvalor de deslocamento (polarização), de forma que o código binário de N corresponde ao número 0e o código binário com todos os bits zerados corresponde a -N. Desta forma, podemos ordenar osnúmeros negativos e positivos comparando diretamente os seus códigos binários.

Page 4: Capítulo 4 Linguagem C: Dados

A Tabela 2 ilustra os números representados em excesso-7 por 4 bits. Como a polarização é 7,qualquer valor inteiro i é representada por i-7 em código binário. Veja, por exemplo, que i=-6 tem arepresentação binária -6+7 = 0001.

Tabela 2 - Excesso-7 em 4 bits.Valor binário Valor representado Valor binário Valor representado

0000 -7 1000 10001 -6 1001 20010 -5 1010 30011 -4 1011 40100 -3 1100 50101 -2 1101 60110 -1 1110 70111 0 1111 8

4.1.3 Pontos Flutuantes

Vale lembrar que a quantidade distinta de elementos, que um conjunto de n bits pode representar deforma biunívoca, é limitada em até 2n. Como podemos representar um número infinito de valorescontidos num intervalo real? Através de uma representação em ponto flutuante.

Um número em ponto flutuante é descrito em notação científica por três elementos: sinal, expoentee mantissa.

S - Sinal E - Expoente F (fraction) - Mantissa

O campo sinal ocupa um bit (bit de sinal) que indica se o número é positivo ou negativo, como nasrepresentações dos números inteiros com sinal. Os campos expoente e mantissa variam conforme aprecisão da notação. De acordo com o padrão IEEE 754-1985 existem dois formatos: precisãosimples (padrão 4 bytes ou 32 bits) e precisão dupla (padrão 8 bytes ou 64 bits). Para a primeira, oexpoente tem 8 bits e a mantissa 23 bits, e para a segunda, o expoente tem 11 bits e a mantissa 52bits.

Um número fracionário f é representado em notação científica, normalizado na base binária(-1)(sinal) × 1.(mantissa) × 2(expoente) = (-1)S × 1.F × 2E. O bit 1 na parte inteira da mantissa não é

armazenado, ele é implícito na representação. O expoente pode ser negativo ou positivo e a notaçãode excesso é usada para representá-lo. Para a precisão simples é usada a notação de excesso-127 epara a precisão dupla, excesso-1023. A mantissa representada pelos n dígitos m1m2m3 … mn deveser tal que f ≈ (-1)S × (2+m1 × 2-1+m2 × 2-2+m3 × 2-3+ … mn × 2-n + 2E). Vale ressaltar que como

a quantidade de n bits para representar a mantissa é limitada, pode existir uma pequena perda deacurácia em relação ao número original.

São destacados três casos especiais:

Page 5: Capítulo 4 Linguagem C: Dados

Representação de zero: se considerássemos que o primeiro dígito não decimal seja sempre 1,

não seria possível representar exatamente o valor 0. Assume-se que quando todos os bits noscampos E e F estiverem 0, então o valor representado é zero.

Valores “denormalizados”: se todos os bits no campo E estiverem em 0, então o valor

representado é 0 (mantissa).

Representação de “infinito”: com todos os bits no campo E em 1 e todos os bits no campo F

em 0.

-Exemplos:

Seguem-se alguns exemplos de conversão entre um valor fracionário e um código IEEE 754-1985.

1. Obtendo o número em decimal correspondente à representação normalizada.

S= 0, então corresponde a um número positivo.E = 1000 0000 na base 2 = 128 em decimal (expoente positivo 128-127=1)F = 1,11 0000.... na base 2 = 1 + 1.2-1 + 1.2-2 = 1,75 em decimal (Lembrando que o número 1antes da vírgula está implícito).

Logo, o número em decimal correspondente ao número em notação de ponto flutuante é:(-1) 0 × 1,75 × 2 (128-127) = 3,5 em decimal

2. Obtendo o número em decimal correspondente à representação normalizada

S = 1, número negativo. E = 0111 1110 = 126 em decimal. F = 1.00000000000000000000001 = 1 + 2-23

Logo, o número em decimal correspondente ao número em notação de ponto flutuante é:-(1 + 2-23) × 2 (126-127) = -0,500000059604644775390625

3. Obtendo o número em decimal correspondente à representação de-normalizada

S = 1, número negativoE = 0F = 0.00000000000000000000001 = 1 × 2-23 (lembrando que nesse caso o 0 antes da vírgulaé implícito, pois E = 0.Logo, o número em decimal correspondente ao número em notação de ponto flutuante é:

-2-23 × 2 (-126) = -2 × (-149) ≈ -1.4×10-45

4. Representando o valor real 3,5 em ponto flutuante.Número positivo, logo S =0.

0 10000000 110 0000 0000 0000 0000 0000

1 0111 1110 000 0000 0000 0000 0000 0001

1 0000 0000 000 0000 0000 0000 0000 0001

Page 6: Capítulo 4 Linguagem C: Dados

O número deve ser dividido por 2 até ser obtido um número da forma 1.F × 2E: 3,5/2 = 1,75-> 1,75 × 2, sendo necessária apenas uma divisão nesse caso, o que corresponde ao expoenteE = 1.Para o cálculo da mantissa deve-se converter o número obtido pela divisão pelo processo demultiplicações sucessivas:

0,75 - parte fracionária0,75 × 2 = 1,50,5 × 2 = 1,00,0 × 2 = 0,0

Sendo assim, temos a representação completa:

Atribuindo os valores às variáveis declaradas num código em C, constatamos na execução doprograma que foram alocados 4 bytes para cada variável declarada como float,

float f0=0.0, f1=3.5, f2=-0.5, f3=-1.4*pow(10,-45);

e os endereços alocados para as quatro variáveis foram 0x20002fec (f0), 0x20002fe8 (f1),0x20002fe4 (f2) e 0x20002fe0 (f3), respectivamente. Compare os valores calculados acima com oscódigos em hexadecimal armazenados nestes endereços da memória. Não se esqueça da ordenaçãolittle endian dos bytes.

Figura 3: Representação em ponto flutuante.

4.1.4 Codificação de Caracteres Alfanuméricos

Em sistemas digitais todos os dados são representados em códigos binários, 0s e 1s. Alfabetos ousímbolos especiais podem ser representados em códigos binários e armazenados digitalmente pormeio dos códigos ASCII (American Standard Code for Information Interchange), ASCII estendidoou EBCDIC (Extended Binary Coded Decimal Interchange Code) e UNICODE.

A Figura 4 sintetiza os 255 códigos ASCII dos caracteres de controle e caracteres alfanuméricos.Sendo 255 caracteres, 8 bits, ou um byte, é suficiente para representar cada código [4].

0 1000 0000 110 0000 0000 0000 0000 0000

Page 7: Capítulo 4 Linguagem C: Dados

Figura 4: 255 códigos ASCII.

Vejamos como a seguinte sequência de caracteres é "traduzida" em códigos binários pelocompilador C no IDE CodeWarrior:

char s[]="#$EA871$#\n\t";

Durante a execução constatamos que foi alocado um bloco de bytes entre os endereços 0x20002fcce 0x20002fd7 para a sequência, ocupando cada caractere um byte. A Figura 5 mostra que nestesendereços estão contidos os valores 0x23, 0x24, 0x45, 0x41, 0x38, 0x37, 0x31, 0x24, 0x23, 0x0A,0x09, 0x00. Comparando-os com os códigos listados na Figura 4, podemos rapidamente concluirque o compilador traduziu os caracteres alfanuméricos em código ASCII.

Figura 5: Representação em ASCII.

Page 8: Capítulo 4 Linguagem C: Dados

4.2 Tipos de Dados Básicos

Vimos na Seção 4.1 que todos os dados precisam de algum espaço na memória para seremarmazenados e, dependendo do tipo de representação utilizado, este espaço pode ser maior oumenor. Como um compilador pode prever o tamanho de memória a ser alocado a um certo dado, umprocessador interpretar corretamente os bits acessados durante a execução de um programa?

Em linguagem de alto nível, os dados, sejam eles variáveis ou constantes, são associados a algumtipo de dados. Um tipo de dados especifica o espaço de memória reservado para armazenar o dado,a forma como se deve interpretar o código binário contido neste espaço, e as operações lógico-aritméticas que podem ser aplicadas sobre tal código binário. A linguagem C suporta 5 tiposbásicos: char, int (inteiros), float (valores de precisão simples em ponto flutuante), void (endereçode um valor de qualquer tipo), double (valores de precisão dupla em ponto flutuante).

É interessante observar que valores do tipo char são sempre representados por 1 byte (8 bits),enquanto o tamanho dos valores do tipo int normalmente tem o tamanho natural dos registradores,ou seja, de um word de um processador. Num processador de 16 bits, com registradores de 16 bits,um valor do tipo int é representado por 16 bits. E num núcleo de 32 bits, é então representado por32 bits. Neste último caso, um valor representado por 16 bits é usualmente conhecido como detamanho de um halfword e um valor representado por 64 bits, doubleword. Dizemos, portanto,que o espaço de memória ocupado pelo tipo int é dependente da máquina, e, a partir dele, infere-seo espaço de memória dos tipos float, void (mesma quantidade de memória do tipo int) e double(duas vezes a quantidade de memória do tipo int).

4.2.1 Conversão entre Tipos

A linguagem C exige que os programas contenham declarações que especifiquem de que tipos dedado são as variáveis que ele utilizará. Porém, ao longo do programa é possível converter os tiposde dados declarados implícita e explicitamente [5].

4.2.1.1 Conversão Implícita

Em linguagem C uma operação envolvendo operandos com um mesmo tipo de dado, tem comoresultado o mesmo tipo de dado. Um exemplo é a divisão de dois números inteiros, como , que⅔em C apresenta 0 como resposta, ao invés de um número decimal 0.6666... Em operações quecontêm operandos de tipos diferentes, os valores dos operandos são convertidos para o mesmo tipoantes da operação ser executada. Tipos mais simples são promovidos implicitamente para tipos maiscomplexos. Usualmente, os operandos têm os seus tipos convertidos para o tipo comum dosoperandos de acordo com a seguinte ordem de precedência: int → unsigned int → float → double→ long double, como mostra Figura 6.

Page 9: Capítulo 4 Linguagem C: Dados

Figura 6: Hierarquia dos tipos de dados (Fonte: [6])

Provido de processadores para operações inteiras, a hierarquia de tipos de dados suportada pelomicrocontrolador da família Kinetis é um pouco diferente da apresentada abaixo. A ordem deprecedência que o nosso processador segue é char → signed char → unsigned short → signed short→ unsigned int.

Ao atribuir o resultado de uma operação a uma outra variável de tipo diferente, ocorre também umaconversão implícita do tipo de dado do lado direito do sinal de atribuição para o tipo de dado davariável no lado esquerdo, conforme mostrado nos dois exemplos que se seguem. -Exemplos:1.

int a=31;int b=5;int resultado;resultado = a/b;

O resultado da operação a/b é 6, do tipo int, pois ambas variáveis “a” e “b” são do mesmo tipo int. O valor a ser atribuído à variável “resultado” é, portanto, 6.

2. float a=31.0, resultado1; char b=5, resultado2; resultado1 = a/b; resultado2 = a/b; O resultado da operação a/b é 6.2, do tipo float, mesmo que “b” seja do tipo char, porque ooutro operando da divisão, variável “a”, é do tipo float, em posição mais alta da hierarquiade promoção. Porém, os conteúdos de “resultado1” e de “resultado2” são 6.2 e 6,respectivamente. Isso acontece, pois, uma vez que “resultado1” e “resultado2” são variáveisde tipos diferentes e a mesma operação é atribuída a elas, o resultado da operação éimplicitamente convertido para os respectivos tipos de “resultado1” e “resultado2”.

4.2.1.2 Conversão Explícita

A linguagem C tem um operador, conhecido por casting, que altera um tipo de dado explicitamente.O formato deste operador aplicado sobre o resultado de uma “expressão” é:

(tipo de dado) expressão

Page 10: Capítulo 4 Linguagem C: Dados

O casting de um tipo tem a maior precedência entre os operadores. Ele é utilizado para que umavariável de um determinado tipo de dado possa ser interpretada como um outro tipo de dadodistinto, sem que haja mudança nos tipos de dado das variáveis utilizadas. O processador conseguetruncar ou estender automaticamente os bits quando o tamanho do espaço de memória varia.

- Exemplos:1.

int x=2;float y;y = (float) x;z = x;printf(“y=%.2f”,y);printf(“z=%.2f”,z);

Ambas as variáveis “y” e “z” assumem o valor 2.00. Nesse caso, a operação que resulta em“y” é um casting explícito. Já a operação que resulta em “z”, é um casting implícito.

2. int z=2, w=5;float y, x;x = z/w;y = (float) z/w;printf(“x = %.2f”, x);printf(“y=%.2f”, y);

A variável “x” assume o valor 0.00, pois como os operandos “z” e “w” são do tipo int, oresultado da operação também é um inteiro, mas ao atribuir o resultado à variável do tipofloat, ocorre o casting implícito apenas no resultado da operação. Já a variável “y” assume ovalor 0.40, pois como o casting explícito é aplicado em “z” antes da divisão ser realizada, otipo float é dividido por um tipo int de mais baixa hierarquia, o que resulta também em umtipo float – promoção automática para um tipo mais alta da hierarquia.

3.int soma = 22, n = 5;double media = (double)soma/n;

O resultado da divisão é 4.4, pois o casting da variável “soma” resulta no tipo de dado float,que ao ser dividido por um tipo int, na posição mais baixa da hierarquia, temos comoresultado um tipo float. Este, por sua vez é atribuído por um tipo na posição mais alta ainda,double. Portanto, “media” fica com o valor 4.4.

4.2.2 Qualificadores dos Tipos

Qualificadores são palavras-chave que modificam as propriedades dos tipos de dados básicos. Osqualificadores podem ser classificados da seguinte forma:

1. Em relação ao sinal: signed e unsigned;2. Em relação ao tamanho: short e long;3. Em relação à variabilidade dos dados: const e volatile.4. Em relação à localização dos dados: auto, register, static e extern.

Page 11: Capítulo 4 Linguagem C: Dados

Os qualificadores de sinal podem ser utilizados, por exemplo, para estabelecer que umadeterminada variável int receba apenas valores positivos (unsigned). No caso do modificadorsigned, não é necessário utilizá-lo, pois, com exceção do tipo char, as variáveis são inicialmentesigned.

Os qualificadores de tamanho, alteram o tamanho do tipo de dado básico, podendo ampliá-lo ( long)ou diminuí-lo (short). Estes são utilizados quando, em geral, sabe-se que o tamanho básico do tipode dados excede ou não é suficiente para representar os valores a serem processados.

-Exemplos:short int a; // 16 bits, números entre -32.768 a 32.767unsigned short int b; // 16 bits, números entre 0 a 65.535unsigned int c; // 32 bits, números entre 0 a 4.294.967.295int d; // 32 bits, números entre -2.147.483.648 a 2.147.483.647long int d; // 32 bits, números entre -2.147.483.648 a 2.147.483.647

Os qualificadores signed, unsigned, long e short são aplicáveis ao int, ou seja, além de termos adistinção entre valores inteiros com sinal e sem sinal, podemos definir através dos qualificadores aquantidade de inteiros representáveis, maior (long) ou menor (short). Eles não podem ser aplicadosao float. Ao double pode-se aplicar apenas o long. E ao char só se aplicam os qualificadores desinal: com sinal (signed) ou sem sinal (unsigned).

O qualificador const é utilizado para estabelecer que uma variável, que pode ser de qualquer tipo,não terá seu valor alterado durante a execução do programa após a atribuição.

-Exemplos:const int nota = 100; /*constante inteira*/const float pi= 3.14; /*constante real*/const char inicial = 'D'; /*constante char*/const char nome_aluno[10] = "ALUNO"; /*constante string*/

O qualificador volatile é adicionado na definição de um tipo de dado para indicar que o valor podeser modificado sem uma instrução explícita do seu programa a qualquer momento, como umavariável que é atualizada pelo sistema de clock ou por fontes externas ao programa em execução.Deixar de informar essa condição das variáveis no programa pode gerar alguns problemas com ocódigo executável gerado sob diferentes configurações. Um código, que funcionava antes quefossem habilitadas interrupções e/ou otimizações do código pelo compilador, pode passar a nãofuncionar corretamente [ 7 ].

-Exemplo:Neste exemplo o programa alterna o sinal do bit 18 da porta digital B.

#define GPIOB_PTOR (*(unsigned int volatile *)0x?????)

Page 12: Capítulo 4 Linguagem C: Dados

void alterna (void){

for (;;) {GPIOB_PTOR = (1<<18);delay(500000);

}}

Caso o endereço não tivesse sido declarado como volatile, o compilador poderia pensar que oendereço sempre recebe um mesmo valor em todas as iterações e poderia otimizar o código naseguinte forma:

#define GPIOB_PTOR (*(unsigned int *)0x400FF04C)void alterna (void){

GPIOB_PTOR = (1<<18);for (;;) {

delay(500000);}

}

Uma observação importante é a de que os qualificadores const e volatile podem ser utilizadosjuntos, para garantir que uma variável não tenha o seu valor alterado dentro do programa, mas que ovalor do endereço por ele indicado pode ser modificado de modo externo ao programa. A instruçãoabaixo está atribuindo um valor fixo de 32 bits (endereço) à variável GPIOB_PTOR e ainda avisa ocompilador que o conteúdo do endereço 0x400FF04C é volátil.

uint32_t volatile * const GPIOB_PTOR = (uint32_t *) 0x400FF04C;

Toda variável é ainda qualificada pela sua forma de armazenamento: auto, extern, static e register[8]. Por padrão, todas as variáveis declaradas dentro do escopo de uma função são qualificadascomo auto. Elas são também conhecidas como variáveis locais com seu tempo de vida limitado aotempo em que a função é executada. Quando as variáveis são definidas fora do escopo das funções,mas dentro de um arquivo de programa, elas são chamadas variáveis globais, cujo acesso écompartilhável entre todas as funções de um mesmo arquivo.

-Exemplo:No trecho de código abaixo a variável “a” é global do arquivo de programa AA.c e a variável “b” élocal da função foo:Arquivo AA.cint a;void foo(void) { short b;}

Page 13: Capítulo 4 Linguagem C: Dados

O qualificador extern é usado para referenciar uma variável global declarada num arquivo deprograma diferente.

-Exemplo:Se ligarmos o seguinte arquivo BB.c como o arquivo AA.c mostrado no exemplo anterior, avariável “a” será compartilhada entre os dois arquivos:Arquivo BB.cextern int a;void foo1(void) { unit8_t b1;}

O qualificador register tem como função declarar variáveis que devem ser armazenadas em umregistrador ao invés da memória RAM, para acessos rápidos. Entretanto, essa declaração nãonecessariamente significa que o valor da variável será armazenado em um registrador. Isso dependedos recursos em hardware e das restrições de implementação. Por isso, ele não é muito utilizado amenos em que casos de otimização para aplicações bem específicas.

Já o qualificador static define que uma variável persiste na memória durante o tempo de vida doprograma, não tendo de ser criada toda a vez que é chamada pelo programa/função, ou descartadatoda a vez que termina o ciclo de execução do programa/função.

-Exemplo:No programa foo(void), a variável “b” declarada como “short b” se extingue automaticamentequando as instruções da função foo não são executadas, mas se adicionarmos o qualificador static àvariável, o conteúdo desta variável é preservado mesmo quando termina de executar foo:

void foo(void) { static short b;}

4.3 Renomeação de Tipos

A falta de uma especificação precisa do tipo int prejudica a portabilidade de um código. Um códigoque funciona bem num microcontrolador de 16 bits (tipo int ocupa 16 bits) com os resultados dasoperações truncados/aproximados em 16 bits pode apresentar outros resultados quando oprocessamento se estende para 32 bits (tipo int ocupa 32 bits). Para que os resultados sejaminvariantes em relação à representação natural de cada microcontrolador, recomenda-se utilizar umconjunto de tipos de dados redefinido de acordo com o tamanho de espaço efetivo de memóriaocupado e não de acordo com o tipo nativo de cada microcontrolador com uso da diretiva typedef.

Page 14: Capítulo 4 Linguagem C: Dados

Tipos redefinidos mais populares adotam a seguinte convenção: s para representar se o tipo éestático ou não, v para representar se o tipo é volátil ou não, u para representar “sem sinal”(unsigned) e int_x para representar um valor inteiro ocupando um espaço de x bits. É comum aindaencontrar no nome o sufixo “t”, denotando “type”.

-Exemplo:uint16/uint16_t e int32/int32_t correspondem, respectivamente, a um valor inteiro sem sinal de 16bits e um valor inteiro com sinal de 32 bits.

As seguintes redefinições dos tipos básicos são suportadas pelo ambiente IDE CodeWarrior:

#typedef signed char int8_t;#typedef unsigned char uint8_t;#typedef volatile signed char vint8_t;#typedef volatile signed char vint8_t;#typedef volatile unsigned char vuint8_t;#typedef signed short int16_t;#typedef unsigned short uint16_t;#typedef signed int int32_t;#typedef unsigned int uint32_t;#typedef static signed int sint32_t;#typedef static unsigned int suint32_t;

4.4 Enumeração

O tipo de dado enumeração (enum) consiste em um conjunto de constantes inteiras representadaspor identificadores definidos pelo usuário [9]. Sua sintaxe é da seguinte forma:

enum < nome > {< constante1 >, < constante2 >, · · ·, < constanteN >}

Para cada uma das palavras-chave da lista é atribuído um valor inteiro. Por padrão, o primeiro valorde uma enum é inicializado com zero, mas pode-se redefinir o valor inicial da lista com um outrovalor, e assim, os valores seguintes são calculados automaticamente a partir do incremento de umaunidade em relação ao seu predecessor. É possível também declarar um valor para cada elemento dalista separadamente.

-Exemplos:Seguem alguns exemplos de declaração com uso de enum:

1. enum meses {Janeiro, Fevereiro, Marco, Abril, Maio, Junho, Julho, Agosto, Setembro,Outubro, Novembro, Dezembro};

2. enum meses_do_ano {Janeiro=1, Fevereiro, Marco, Abril, Maio, Junho, Julho, Agosto,Setembro, Outubro, Novembro, Dezembro};

Page 15: Capítulo 4 Linguagem C: Dados

3. enum _tipo_led {VERMELHO, /*!< vermelho */VERDE, /*!< verde */AZUL /*!<azul */};

Baseado no maior valor ao qual os elementos podem associar, o compilador consegue determinar otamanho máximo do espaço necessário para armazená-los. Portanto, este tipo de dado não onera ouso da memória e torna o código mais inteligível.

-Exemplo:O seguinte trecho de código ilustra como podemos usar os nomes dos meses nos porgramas semque estes nomes sejam representados no código ASCII. Observe na Figura 7 que os tiposenumerados não são caracteres alfanuméricos, conforme mostra o valor armazenado no endereço0x20002fd7 da memória pelo código traduzido com o compilador C no ambiente IDE CodeWarriora partir das instruções:

enum meses {Janeiro=1, Fevereiro, Marco, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro};

enum meses enum_meses;enum_meses = Dezembro;

O compilador reconheceu automaticamente que só há 12 itens na lista e alocou um espaço de umbyte para armazenar os diferentes valores. Após a execução da instrução “enum_meses =Dezembro” o conteúdo do endereço 0x20002fd7 passou a ser 0x0c.

Figura 7: Ocupação de memória pelo tipo enum.

Se adicionarmos as seguintes instruções:

int i;for(i=Janeiro;i<=Dezembro;i++) printf("\n%d",i);}

teremos como a saída do programa a sequência: 1-2-3-4-5-6-7-8-9-10-11-12. Note que declaramosavariável “i” como um tipo de dado inteiro, ou seja, internamente, o processador manipulou oselementos do tipo enum meses como valores inteiros.

Page 16: Capítulo 4 Linguagem C: Dados

4.5 Ponteiro

Como vimos até agora, uma variável resume-se, na verdade, a um espaço de memória que possuium determinado endereço no qual é armazenado um valor de um certo tipo de dado. Um ponteiro éuma variável associada a um espaço da memória onde é armazenado um endereço da própriamemória [10]. Sendo assim, um ponteiro tem sempre um tamanho fixo, correspondente ao tamanhodos endereços de um processador independentemente do tipo de dado do valor que está armazenadonaquele endereço. No nosso processador Cortex-M0+ este tamanho é 4 bytes.

Sempre declaramos uma variável como ponteiro a um certo tipo de dado com uso do operador “*”no formato:

<tipo de dado *> <nome_do_ponteiro>;

que significa que a variável <nome_do_ponteiro> é um ponteiro, ou o seu valor é o endereço, deum valor do <tipo de dado>.

Em C existem operadores especiais que permitem acessar o endereço de um valor e o conteúdo deum endereço. Em uma operação, diferentemente da declaração, o compilador traduz o operador "*"aplicado sobre uma variável ponteiro por “o conteúdo da variável ponteiro” e o operador “&”aplicado sobre uma variável comum por “o endereço da variável”.

-Exemplo:No seguinte trecho de código:

short b, a = 16;short *ptr=NULL;b = 0;ptr = &a;

b = *ptr;

foi declarado como ponteiro ao tipo de dado short a variável “ptr”. Mesmo atribuindo o valor 0 àvariável “b”, vamos ver que depois da execução da 5a linha, o conteúdo da variável “b” ficará igualao conteúdo do endereço para o qual a variável “ptr” aponta (*ptr). E qual é o conteúdo da variável“ptr”? Pela 4a linha podemos ver que é o endereço da variável “a” (&a). Executando este trecho decódigo no ambiente IDE CodeWarrior, vimos que as variáveis “b”, “a” e “ptr” foram alocados nosendereços 0x20002fce, 0x20002eee e 0x20002fc8, respectivamente. A Figura 8 mostra o mapa damemória após a execução da 5a linha. Vale lembrar que a ordenação dos bytes é little endian. Vejaque o conteúdo da variável “ptr” (0x20002fc8) é exatamente o endereço da variável “a”(0x20002eee). O tamanho da variável “a” é 2 bytes (tipo de dado short), mas o seu endereço érepresentado por 4 bytes. Veja também o conteúdo da variável “b” (0x20002fce). É exatamenteigual ao conteúdo da variável “a” ao compararmos os valores dos 2 bytes adjacentes.

Page 17: Capítulo 4 Linguagem C: Dados

Figura 8: Alocação de memória para ponteiros.

Cabe reforçar aqui que, para um ponteiro, o compilador só aloca um espaço correspondente a umendereço e o seu conteúdo é interpretado como endereço da memória. Portanto,

antes de usá-lo deve-se garantir que o seu conteúdo seja um endereço válido para que o

sistema não gere exceções. Na prática, recomenda-se a inicializar uma variável ponteirosempre com NULL, que é uma constante de valor 0 definida na biblioteca <stdlib.h>, queindica que o ponteiro não aponta para um endereço da memória específico;

não se pode atribuir um valor maior que 4 bytes a uma variável ponteiro; senão pode ocorrer

invasão do espaço de memória de outros dados.

4.6 Void

Em inglês void significa vazio. Este tipo indica que não há nenhum dado disponível. Ele só éutilizado em três situações bem específicas:

1. Quando uma rotina ou uma função não retorna nenhum valor, ela é declarada como do tipovoid. Por exemplo, a função free que libera um bloco de memória endereçado pelo “ptr”,void free (void *ptr);

2. Quando a lista de argumentos de uma função ou subrotina é vazia. Por exemplo, a funçãoclock que retorna o tempo consumido por um processo, clock_t clock (void);

3. Quando se refere a um endereço da memória, sem especificar o tipo de dado que éarmazenado naquela posição de memória. Por exemplo, a função calloc que aloca um blococom “num” elementos de tamanho “size”, void *calloc (size_t num, size_t size). Na Seção4.7 detalharemos mais sobre como processar um bloco alocado dinamicamente.

Page 18: Capítulo 4 Linguagem C: Dados

4.7 Alocação de Espaço de Memória

O processo de alocação de memória consiste em reservar parte do espaço de memória para aexecução de um programa ou um processo. Essa alocação é feita por um processo de gerenciamentoda memória. A linguagem C permite gerenciar a memória das seguintes formas: estaticamente,automaticamente e dinamicamente [11]. O espaço de memória alocado para cada variável dependeda quantidade de bytes que ele precisa para representar os dados.

Variáveis alocadas de forma estática no código do programa persistem na memória por todo otempo de execução do programa, independentemente de serem necessárias ou não. Quando sedeclara uma variável de certo tipo de dado, vimos que o compilador aloca automaticamente aquantidade de bytes necessários para representar os seus valores.

Já variáveis automáticas são alocadas em uma pilha numa chamada de rotinas. Elas são empilhadasantes de entrar na rotina e desempilhadas antes de retorna ao ponto de entrada. O valor dessasvariáveis não persiste para múltiplas chamadas de funções. Para esse tipo de variável, um tamanhode memória fixo é alocado durante o tempo de execução.

Para os casos em que o tamanho de memória necessário não é conhecido a priori, tanto a alocaçãoestática quanto a alocação automática de memória não são adequadas. A alocação dinâmica dememória, em que a memória é manipulada de forma explícita dentro do código em C, seria asolução. Numa alocação dinâmica a memória pode ser alocada e liberada conforme seu uso durantea execução do programa. Isso permite que o programa obtenha mais espaço de memória emexecução. As seguintes funções de manipulação do espaço da memória podem ser utilizadas nonosso programa em C, se incluirmos o arquivo <stdlib.h> que contém o protótipo destas funções:

Quadro 1 – Descrição das funções em linguagem C referentes à alocação de memóriaFunção Finalidade

calloc - void *calloc(int N, int size);Aloca um bloco de N elementos, com cadaelemento com tamanho em bytes conforme size.

malloc - void *malloc(int N); Aloca um bloco de N bytes.

free - void free(void * ptr);Libera um bloco da memória previamente alocadode endereço especificado pelo ponteiro ptr

realloc - void *realloc(void * ptr, int newsize);Realoca um espaço de memória dado por ptrestendendo-o para newsize em bytes.

Observe que o tipo de dado retornado por estas funções é void. Vimos na Seção 4.6 que esta é umaforma de definir um endereço sem especificar o tipo de dado do seu conteúdo. Se quisermos que obloco de memória seja utilizado para armazenar um tipo de dado específico, podemos fazer umcasting no endereço retornado, convertendo-o do tipo void para o tipo desejado. Um dosargumentos comum a todas as funções de (re)alocação de espaço de memória é a quantidade debytes a ser alocado. A função sizeof pode ser útil na determinação dos bytes requeridos para umcerto tipo de dado.

Page 19: Capítulo 4 Linguagem C: Dados

-Exemplo:No trecho do código abaixo é alocado um bloco de 20 elementos de tamanho igual a um dado dotipo unsigned short e atribuiu-se o valor 0 a todos os bytes alocados. Com uso de sizeof foideterminado que o tamanho do tipo de dado unsigned short é 2 bytes. Portanto, serão alocados 20*2= 40 bytes de memória. Com o casting (unsigned short *) os 40 bytes serão alinhados de 2 em 2bytes e cada 2 bytes processados como um valor do tipo unsigned short.

uint8_t i; unsigned short *us; us = (unsigned short *)malloc(20*sizeof(unsigned short)); for (i = 0; i<20; i++) { us[i]=0; }

Executando o código acima no IDE CodeWarrior, obtivemos o endereço 0x20002fc0 para oponteiro “us” e o endereço inicial do bloco alocado é 0x1ffff048. A Figura 9 mostra o mapa dememória após a execução do laço que zera o conteúdo do bloco.

Figura 9: Espaço de memória alocado dinamicamente e preenchido com 0.

Vale frisar aqui que o recurso de memória não é infinito, por isso é importante adquirir o bomhábito de liberar a memória quando não é mais utilizado, com uso da função free que desaloca amemória.

4.8 Estruturas

A linguagem C oferece alguns recursos que nos permitem construir tipos de dados mais complexosa partir dos tipos de dados básicos para facilitar a organização dos dados e tornar o programa maislegível.

4.8.1 Struct

Uma struct define um conjunto de variáveis em uma mesma estrutura [12]. Estas variáveis podemser de diferentes tipos de dados. A estrutura é utilizada para agrupar dados distintos, mas que fazemparte de um mesmo registro de informação.

-Exemplo:Podemos organizar numa struct um registro de endereço, que compreende o nome do logradouro, onúmero, o CEP, nome da cidade e estado, e cada uma dessas informações é melhor representada porum certo tipo de dado

Page 20: Capítulo 4 Linguagem C: Dados

struct endereco{char logradouro[100];int numero;int cep;char cidade[50];char estado [50];

};

Como os outros tipos de dados, podemos utilizar o tipo de dado struct definido para declarar asvariáveis. Nos trechos de código abaixo, mostramos duas formas distintas para declarar as variáveis“end1” e “endA” do tipo de dado “struct endereco”, e o ponteiro “end” ao tipo de dado “structendereco *”:

struct endereco{char logradouro[100];int numero;char cidade[50];char estado[50];

} end1, endA;

int main( ){struct endereco end1, endA, *end;....

}

A inicialização de uma struct e o acesso aos seus elementos varia conforme a variável do tipo structdeclarada, se é um ponteiro ou se é uma estrutura normal. Quando se trata de um ponteiro a um tipostruct, o acesso aos elementos de uma struct é pelo operador “->”, já para uma variável do tipostruct é utilizado o operador “.”.

-Exemplo:Compilando o seguinte trecho de código com o compilador de C no IDE CodeWarrior,

struct endereco{char logradouro[100];int numero;char cidade[50];char estado[50];

} end1;struct endereco *end;

memcpy (end1.logradouro,"Avenida Albert Einstein", 24*sizeof(char));end1.numero = 400;memcpy (end1.cidade,"Campinas", 9*sizeof(char));memcpy (end1.estado,"Sao Paulo", 10*sizeof(char));end = &end1;

foram alocados os seguintes endereços contíguos na memória 0x20002ef0, 0x20002f54,0x20002f58, 0x20002f8a para os itens logradouro, número, cidade e estado, respectivamente. Alémdisso, foi alocado para o ponteiro “end” o endereço 0x20002fd0. Veja na Figura 10 como ficou oconteúdo da memória após as atribuições:

Page 21: Capítulo 4 Linguagem C: Dados

Figura 10: Alocação de memória para uma struct.

Para simplificar, é muito comum redefinir um tipo de dado struct com um nome menor com uso dadiretiva typedef.

-Exemplo:Foram renomeados o tipo struct _cor e o tipo struct endereco para cor e end nos seguintes trechosde código:

typedef struct _cor {enum _estado_led vermelho; /*!<

estado do led*/enum _estado_led verde;enum _estado_led azul;

} cor;

int main( ){cor cores_estaticas =

{APAGADO,APAGADO,APAGADO};}

typedef struct endereco{...

}end;

int main( ){end end1, endA;

}

É possível também que uma struct ou um ponteiro a uma struct esteja incluída dentro de uma outrastruct. A definição de um ponteiro a um tipo de dado struct que corresponde ao tipo de dado structem que ele é integrado é uma estratégia muito utilizada para construir uma lista ligada de instânciasde uma struct.

Page 22: Capítulo 4 Linguagem C: Dados

-Exemplo:Neste exemplo é definido um ponteiro ao tipo de dado struct endereco dentro da struct endereco.Isso permite que um registro de endereço seja ligado ao próximo através do ponteiro armazenado nomembro “prox_end”.

struct endereco {char logradouro[100];int numero;...struct endereco *prox_end;

}

4.8.2 Arranjo

É possível alocar um bloco contíguo de bytes na memória para armazenar uma sequência de dadosde mesmo tipo, denominado um arranjo (array) [13]. O tipo de dado de um arranjo é o tipo de dadodos seus elementos. O primeiro elemento de um arranjo se inicia sempre na posição 0 e seuselementos são endereçados por índices inteiros não negativos. Para um arranjo com N elementos,seu último elemento possui índice N-1.

Um arranjo é definido da seguinte forma:

<tipo do dado> <nome do arranjo> [<número de elementos do arranjo>];

A quantidade de elementos num arranjo pode ser fixada durante a sua declaração, ou seja, através deuma alocação estática, ou durante o tempo de execução por meio de uma alocação dinâmica.

Alocação estáticachar disciplina[5];int ra_alunos[10];

Alocação dinâmicachar *magenta;magenta = malloc(sizeof(char)*8);

Em termos de ocupação de memória, um arranjo pode ser visto como uma sequência de blocos dememória, contendo em cada bloco o valor de um elemento do arranjo:

disciplina[0] disciplina[1] disciplina[2] disciplina[3] disciplina[4]

Nesse caso, o número de elementos do array disciplina é 5 e o tamanho do array corresponde a 5vezes o tamanho de um char (1 byte), que é o tipo do dado dos seus elementos.

A inicialização de um array pode ser feita na declaração, não sendo necessário definir o tamanho doarranjo, pois, nesse caso, o compilador determina o tamanho do arranjo por sua quantidade deelementos. É sempre uma boa prática inicializar um arranjo, pois não se garante que o espaçoalocado a ele seja sempre devidamente inicializado com 0.

Page 23: Capítulo 4 Linguagem C: Dados

-Exemplo:Duas formas distintas de inicializar um arranjo:

int ra_alunos[5]={1, 2, 3, 4, 5};int ra_alunos[]={1, 2, 3, 4, 5}.

Os elementos de um arranjo podem ser acessados e manipulados de forma separada por meio dooperador [], conforme mostra as atribuições de 1, 2 e 3 aos elementos 0, 1 e 2 do arranjo:

ra_alunos[0]=1;ra_alunos[1]=2;ra_alunos[2]=3;

Outra forma de acessar um elemento de arranjo é pela adição do deslocamento (offset) do elementoem relação ao endereço inicial do bloco do arranjo, pois um arranjo é um bloco contíguo de bytescujos elementos podem ser obtidos diretamente com a soma do endereço inicial mais a quantidade ide blocos que se deve deslocar para chegar no elemento.

-Exemplo:A seguinte instrução atribui à variável a o conteúdo do quinto elemento do arranjo:

a = *(ra_alunos+4);

O operador “*” é só para indicar que se deve mover para a variável “a” o conteúdo do endereço enão o endereço propriamente dito.

4.8.3 String

Em linguagem C, uma cadeia de caracteres, ou uma string, é um arranjo especial de caracteresalfanuméricos, do tipo char, terminado pelo caractere nulo ('\0') que indica o fim da string [14].Uma string é diferenciada por aspas duplas e é declarada da mesma forma que um vetor (ou umarranjo) de char:

char nome_da_string[tamanho].

Deve-se lembrar que o caractere delimitador (‘\0’) deve ser incluído no tamanho da string aodeclará-la e que a primeira posição do vetor começa em zero. Veja os exemplos na Seção 4.1.4 e4.8.1 com strings. Todas as strings armazenadas na memória terminam com o hexadecimal 0x00.

-Exemplo:Para a declaração abaixo,

char disciplina[6] = ”EA871”;

o compilador alocaria 6 bytes na memória e armazenaria neles os seguintes caracteres:

E A 8 7 1 \0

Page 24: Capítulo 4 Linguagem C: Dados

Uma string pode ser inicializada de diversas formas, como um ponteiro, ou também de formasemelhante a um array de char, sendo que seu tamanho pode ou não ser previamente definido. Pois,vimos que todas estas formas são traduzidas pelo compilador num bloco contíguo de bytes. Paradeterminadas inicializações da string, não é necessário incluir o delimitador nulo. Ele é inseridoautomaticamente ao final da string.

-Exemplos:Tamanho pré-definido

char disciplina[6] = “EA871”;char disciplina[6] = {‘E’,’A’, ‘8’, ‘7’, ‘1’};char disciplina[100] = “EA871”;

Tamanho indefinidochar disciplina[] = “EA871”;char disciplina[]= {‘E’,’A’, ‘8’, ‘7’, ‘1’,’\0’};char *disciplina = “EA871”;

É importante frisar que, para inicializar tanto um arranjo como uma string vazios, deve-se observar as seguintes equivalências:

char disciplina[10] = ""; equivale a char disciplina[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};char disciplina[10] = " "; equivale a char disciplina[10] = {' ', 0, 0, 0, 0, 0, 0, 0, 0, 0};char disciplina[10] = "a"; equivale a char disciplina[10] = {'a', 0, 0, 0, 0, 0, 0, 0, 0, 0};

Para a manipulação de strings, é necessário incluir o arquivo <string.h> que contém o protótipo dasseguintes funções:

Quadro 2 – Descrição das funções em linguagem C referentes à manipulação de stringsFunção Finalidade

strcpy: strcpy(str1, str2); Copia str2 para str1.strcat: strcat(str1, str2); Concatena str2 ao final de str1.strlen: strlen(str1); Retorna o tamanho de str1, a menos do caractere nulo no fim da

string.strcmp: strcmp(str1, str2); Compara as strings – retorna 0 caso elas sejam iguais; retorna um

inteiro negativo caso str1 seja menor que str2 (alfabeticamente);retorna um inteiro positivo caso str1 seja maior que str2. Essa funçãoconsidera se os caracteres são minúsculos ou maiúsculos (casesensitive).

strchr: strchr(s1, letra); Retorna um ponteiro para a primeira ocorrência de um caracter nastring.

strstr: strstr(s1, s2); Localiza uma substring (s2) dentro de uma string (s1), retornando umponteiro para a primeira ocorrência da substring.

strtok: strtok(s1, delim); Divide uma string conforme a ocorrência do delimitador (símbolo).

Page 25: Capítulo 4 Linguagem C: Dados

4.9 Constantes

É comum utilizar (valores) constantes, ou seja, valores que não variam ao longo da execução de umcódigo. Há diversas forma para declarar um (valor) constante em código C:

1. const int valor=5, como vimos na Seção 4.2.22. #define valor 5, como vimos na Seção 2.2.13. enum {valor=5}, como vimos na Seção 4.6

Qual é a melhor forma para declará-lo em sistemas embarcados? Vai depender da aplicação. Oimportante é ter em mente qual efeito de cada uma das declarações. Para a primeira declaração ovalor constante é usualmente armazenado numa memória ROM; portanto, é considerado de fato umvalor constante. A segunda declaração é uma simples diretiva que instrui o pré-processador asubstituir todas palavras “valor” no código por “5” antes da compilação; portanto, é tambémconhecido como declaração de um literal. E a terceira declaração define “valor” como umidentificador de uma variável a ser armazenada na memória RAM que tem um conjunto restrito devalores. No caso, só o valor 5.

4.10 Alocação de Dados em Sistemas Embarcados via C

Vimos neste capitulo que a escolha de tipos de dados tem impacto direto na ocupação da memória ea quantidade de memória disponível é bem limitada, quatro regras de escolha são recomendadas nodesenvolvimento de programas para microcontroladores que suportam somente operações lógio-aritméticas inteiras [15]:

1. Utilize o tipo de tamanho de memória suficiente para representar os dados a seremprocessados;

2. Utilize o tipo de dados sem sinal (unsigned) quando possível;3. Converta sempre os dados de um tipo para um tipo de tamanho menor, porém suficiente

para representar os dados, nas operações;4. Evite os tipos float e double quando a eficiência é prioritária.

Mostramos que a linguagem C suporta alocação dinâmica de memória. Isso pode nos ajudar emcontornar o problema de falta de memória em sistemas embarcados, reusando espaços que não sãomais necessários ao longo da execução de um programa. Porém, é importante lembrar que ela podeter um impacto negativo no desempenho do sistema.

Finalmente, apresentamos ainda diversos mecanismos que a linguagem C nos oferece paraorganizar as nossas variáveis de forma a tornar o nosso código mais inteligível sem onerar aocupação da memória e o tempo de execução. Cabe, no entanto, ao projetista usá-los de formaadequada na implementação do seu software em linguagem C para sistemas embarcados.

Page 26: Capítulo 4 Linguagem C: Dados

Referências

[1] NXP. CodeWarrior Development Studio Common Features Guideftp://ftp.dca.fee.unicamp.br/pub/docs/ea871/manuais/CWCFUG.pdf[2] Chua Hock-Chuan. A Tutorial on Data Representation: Integers, Floating-point Numbers, and Characters,https://www.ntu.edu.sg/home/ehchua/programming/java/DataRepresentation.html[3] CS Education Research Group. Data Representation. http://csfieldguide.org.nz/en/chapters/data-representation.html[4] Wikipedia. ASCII. http://en.wikipedia.org/wiki/ASCII [5] Robson R. Linhares. Aspectos básicos de linguagem C. http://www.dainf.cefetpr.br/~robson/prof/common/c/aspec.htm[6] Técnicas de Linguagem de Programação. Conceitos Gerais - Tipos de dados. http://www.prof2000.pt/users/famaral/ig/tlp/variaveis.htm[7] Nigel Jones. Introduction to the volatile keyword. http://www.embedded.com/electronics-blogs/beginner-s-corner/4023801/Introduction-to-the-Volatile-Keyword[8] Programiz. Scope and Lifetime of a variable.http://www.programiz.com/c-programming/c-storage-class[9] C4Learn.com. C enum: Enumerated Types. http://www.c4learn.com/c-programming/c-enum/[10] Wikibooks. Programar em C/Ponteiros. http://pt.wikibooks.org/wiki/Programar_em_C/Ponteiros[11] Tep Dobry. Dynamic Allocation. http://www-ee.eng.hawaii.edu/~tep/EE160/Book/chap14/section2.1.2.html[12] Tutorialspoint. C – Structures. http://www.tutorialspoint.com/cprogramming/c_structures.htm[13] Fresh2fresh.com. C – Array. http://fresh2refresh.com/c-programming/c-array/[14] Tutorialspoint. C – Strings. http://www.tutorialspoint.com/cprogramming/c_strings.htm[15] Derrick Klotz. C for Embedded Systems Programming.http://www.nxp.com/files/training/doc/dwf/AMF_ENT_T0001.pdf

Setembro/2016