Compreendendo o C incorporado:o que são estruturas?
Depois de apresentar as estruturas, daremos uma olhada em algumas das aplicações importantes desse poderoso objeto de dados. Em seguida, examinaremos a sintaxe da linguagem C para declarar uma estrutura. Por fim, apresentaremos brevemente o requisito de alinhamento de dados. Veremos que podemos reduzir o tamanho de uma estrutura simplesmente reorganizando a ordem de seus membros.
Este artigo fornece algumas informações básicas sobre estruturas em programação C incorporada.
Depois de apresentar as estruturas, daremos uma olhada em algumas das aplicações importantes desse poderoso objeto de dados. Em seguida, examinaremos a sintaxe da linguagem C para declarar uma estrutura. Por fim, apresentaremos brevemente o requisito de alinhamento de dados. Veremos que podemos reduzir o tamanho de uma estrutura simplesmente reorganizando a ordem de seus membros.
Estruturas
Várias variáveis do mesmo tipo que estão logicamente relacionadas umas às outras podem ser agrupadas como uma matriz. Trabalhar em um grupo em vez de uma coleção de variáveis independentes nos permite organizar os dados e usá-los de maneira mais conveniente. Por exemplo, podemos definir a seguinte matriz para armazenar as últimas 50 amostras de um ADC que digitaliza uma entrada de voz:
uint16_t voice [50];
Observe que uint16_t é um tipo inteiro sem sinal com uma largura de exatamente 16 bits. Isso é definido na biblioteca padrão C stdint.h , que fornece tipos de dados de um comprimento de bit específico independente das especificações do sistema.
Os arrays podem ser usados para agrupar várias variáveis do mesmo tipo de dados. E se houver uma conexão entre variáveis de diferentes tipos de dados? Podemos tratar essas variáveis como um grupo em nosso programa? Por exemplo, suponha que precisamos especificar a taxa de amostragem do ADC que gera a voz matriz acima. Podemos definir uma variável flutuante para armazenar a taxa de amostragem:
float sample_rate;
Embora as variáveis voz e sample_rate estão relacionados entre si, eles são definidos como duas variáveis independentes. Para associar essas duas variáveis entre si, podemos usar uma poderosa construção de dados da linguagem C chamada estrutura. As estruturas nos permitem agrupar diferentes tipos de dados e lidar com eles como um único objeto de dados. Uma estrutura pode incluir diferentes tipos de tipos de variáveis, como outras estruturas, ponteiros para funções, ponteiros para estruturas, etc. Para o exemplo de voz, podemos usar a seguinte estrutura:
struct record {uint16_t voice [50]; float sample_rate;};
Neste caso, temos uma estrutura chamada registro que tem dois membros ou campos diferentes:o primeiro membro é uma matriz de uint16_t elementos, e o segundo membro é uma variável do tipo float. A sintaxe começa com a palavra-chave struct . A palavra após a palavra-chave struct é um nome opcional usado para fazer referência à estrutura posteriormente. Discutiremos outros detalhes de definição e uso de estruturas no restante do artigo.
Por que as estruturas são importantes?
O exemplo acima aponta uma importante aplicação de estruturas, ou seja, a definição de objetos de dados dependentes de aplicativos que podem associar variáveis individuais de diferentes tipos entre si. Isso não só leva a uma maneira eficiente de manipular os dados, mas também nos permite implementar estruturas especializadas chamadas estruturas de dados.
As estruturas de dados podem ser usadas para vários aplicativos, como mensagens entre dois sistemas incorporados e armazenamento de dados coletados de um sensor em locais de memória não contíguos.
Figura 1. As estruturas podem ser usadas para implementar uma lista vinculada.
Além disso, as estruturas são objetos de dados úteis quando o programa precisa acessar os registros de um periférico microcontrolador mapeado na memória. Vamos dar uma olhada nos aplicativos de estrutura no próximo artigo.
Figura 2. Mapa de memória de um MCU STM32. Imagem cortesia de Embedded Systems with ARM.
Declarando uma estrutura
Para usar estruturas, primeiro precisamos especificar um modelo de estrutura. Considere o código de exemplo abaixo:
struct record {uint16_t voice [4]; float sample_rate;};
Isso especifica um layout ou modelo para criar as variáveis futuras deste tipo. Este modelo inclui uma matriz de uint16_t e uma variável do tipo float. O nome do modelo é registro , e isso vem depois da palavra-chave struct . Vale a pena mencionar que não há alocação de memória para armazenar um modelo de estrutura. A alocação de memória ocorre somente depois que uma variável de estrutura baseada neste layout é definida. O código a seguir declara a variável mic1 do modelo acima:
registro de estrutura mic1;
Agora, uma seção de memória é alocada para a variável mic1 . Tem espaço para armazenar os quatro uint16_t elementos da matriz e uma variável float.
Os membros de uma estrutura podem ser acessados usando o operador de membro (.). Por exemplo, o código a seguir atribui 100 ao primeiro elemento da matriz e copia o valor de sample_rate para o fs variável (que deve ser do tipo float).
mic1.voice [0] =100; fs =mic1.sample_rate;
Outras maneiras de declarar uma estrutura
Vimos uma maneira de declarar estruturas na seção anterior. A linguagem C suporta alguns outros formatos que serão analisados nesta seção. Você provavelmente irá manter um formato em todos os seus programas, mas estar familiarizado com os outros pode ser útil às vezes.
A sintaxe geral para declarar o modelo de uma estrutura é:
struct tag_name {type_1 member_1; type_2 member_2; … Type_n member_n;} variable_name;
O tag_name e nome_variável são identificadores opcionais. Normalmente veremos pelo menos um desses dois identificadores, mas há casos em que podemos eliminar os dois.
Sintaxe 1: Quando tag_name e nome_variável estão presentes, estamos definindo a variável de estrutura logo após o modelo. Usando essa sintaxe, podemos reescrever o exemplo anterior da seguinte maneira:
struct record {uint16_t voice [4]; float sample_rate;} mic1;
Agora, se precisarmos definir outra variável ( mic2 ), nós podemos escrever
registro de estrutura mic2;
Sintaxe 2: Apenas nome_variável está incluído. Usando essa sintaxe, podemos reescrever o exemplo da seção anterior da seguinte maneira:
struct {uint16_t voice [4]; float sample_rate;} mic1;
Neste caso, temos que definir todas as nossas variáveis logo após o modelo e não podemos definir qualquer outra variável posteriormente em nosso programa (porque o modelo não tem um nome e não podemos nos referir a ele mais tarde).
Sintaxe 3: Nesse caso, não há tag_name ou nome_variável . Os modelos de estrutura definidos dessa maneira são chamados de estruturas anônimas. Uma estrutura anônima pode ser definida dentro de outra estrutura ou união. Um exemplo é dado abaixo:
struct test {// Estrutura anônima struct {float f; char a; };} test_var;
Para acessar os membros da estrutura anônima acima, podemos usar o operador membro (.). O código a seguir atribui 1.2 ao membro f .
test_var.f =1.2;
Como a estrutura é anônima, acessamos seus membros usando o operador membro apenas uma vez. Se tivesse um nome como no exemplo a seguir, teríamos que usar o operador membro duas vezes:
teste de estrutura {estrutura {float f; char a; } aninhado;} test_var;
Nesse caso, devemos usar o seguinte código para atribuir 1,2 a f :
test_var.nested.f =1.2;
Como você pode ver, as estruturas anônimas podem tornar o código mais legível e menos detalhado. Também é possível usar a palavra-chave typedef junto com uma estrutura para definir um novo tipo de dados. Veremos esse método em um artigo futuro.
Layout de memória para uma estrutura
O padrão C garante que os membros de uma estrutura serão localizados na memória um após o outro na ordem em que os membros são declarados dentro da estrutura. O endereço de memória do primeiro membro será o mesmo que o endereço da própria estrutura. Considere o seguinte exemplo:
struct Test2 {uint8_t c; uint32_t d; uint8_t e; uint16_t f;} MyStruct;
Quatro posições de memória serão alocadas para armazenar as variáveis c, d, e e f. A ordem das localizações de memória corresponderá à de declarar os membros:a localização para c terá o endereço mais baixo, então d, e e, finalmente, f aparecerá. Quantos bytes precisamos para armazenar esta estrutura? Considerando o tamanho das variáveis, sabemos que, pelo menos, 1 + 4 + 1 + 2 =8 bytes são necessários para armazenar esta estrutura. No entanto, se compilarmos este código para uma máquina de 32 bits, observaremos surpreendentemente que o tamanho de MyStruct tem 12 bytes em vez de 8! Isso se deve ao fato de que um compilador tem certas restrições ao alocar memória para diferentes membros de uma estrutura. Por exemplo, um inteiro de 32 bits pode ser armazenado apenas em locais de memória cujo endereço é divisível por quatro. Essas restrições, conhecidas como requisitos de alinhamento de dados, são implementadas para permitir que o processador acesse as variáveis com mais eficiência. O alinhamento de dados leva a algum espaço desperdiçado (ou preenchimento) no layout da memória. Este tópico é apresentado apenas aqui; veremos os detalhes no próximo artigo desta série.
Figura 3. O alinhamento de dados leva a algum espaço desperdiçado (ou preenchimento) no layout de memória.
Conhecendo os requisitos de alinhamento de dados, podemos reorganizar a ordem dos membros dentro de uma estrutura e tornar o uso da memória mais eficiente. Por exemplo, se reescrevermos a estrutura acima conforme a seguir, seu tamanho diminuirá para 8 bytes em uma máquina de 32 bits.
struct Test2 {uint32_t d; uint16_t f; uint8_t c; uint8_t e;} MyStruct;
Para um sistema embarcado com restrição de memória, é uma economia significativa reduzir o tamanho de um objeto de dados de 12 bytes para 8 bytes, particularmente quando um programa requer muitos desses objetos de dados.
O próximo artigo discutirá o alinhamento de dados em mais detalhes e examinará alguns exemplos de uso de estruturas em sistemas embarcados.
Resumo
- As estruturas nos permitem definir objetos de dados dependentes de aplicativos que podem associar variáveis individuais de diferentes tipos umas às outras. Isso leva a um meio eficiente de manipulação de dados.
- Estruturas especializadas, chamadas de estruturas de dados, podem ser usadas para vários aplicativos, como mensagens entre dois sistemas incorporados e armazenamento de dados coletados de um sensor em locais de memória não contíguos.
- As estruturas são úteis quando precisamos acessar os registros de um periférico microcontrolador mapeado em memória.
- Podemos tornar o uso da memória mais eficiente reorganizando a ordem dos membros dentro de uma estrutura.
Para ver uma lista completa dos meus artigos, visite esta página.
Integrado