O problema
Em um contexto embarcado bare-metal de baixo nível , gostaria de criar um espaço em branco na memória, dentro de uma estrutura C ++ e sem nenhum nome, para proibir o usuário de acessar esse local de memória.
No momento, consegui colocar um campo de uint32_t :96;
bits feio que ocupará convenientemente o lugar de três palavras, mas gerará um aviso do GCC (Bitfield muito grande para caber em uint32_t), o que é bastante legítimo.
Embora funcione bem, não é muito claro quando você deseja distribuir uma biblioteca com várias centenas desses avisos ...
Como faço isso corretamente?
Por que existe um problema em primeiro lugar?
O projeto no qual estou trabalhando consiste em definir a estrutura de memória de diferentes periféricos de uma linha inteira de microcontroladores (STMicroelectronics STM32). Para fazer isso, o resultado é uma classe que contém uma união de várias estruturas que definem todos os registros, dependendo do microcontrolador alvo.
Um exemplo simples para um periférico bastante simples é o seguinte: um General Purpose Input / Output (GPIO)
union
{
struct
{
GPIO_MAP0_MODER;
GPIO_MAP0_OTYPER;
GPIO_MAP0_OSPEEDR;
GPIO_MAP0_PUPDR;
GPIO_MAP0_IDR;
GPIO_MAP0_ODR;
GPIO_MAP0_BSRR;
GPIO_MAP0_LCKR;
GPIO_MAP0_AFR;
GPIO_MAP0_BRR;
GPIO_MAP0_ASCR;
};
struct
{
GPIO_MAP1_CRL;
GPIO_MAP1_CRH;
GPIO_MAP1_IDR;
GPIO_MAP1_ODR;
GPIO_MAP1_BSRR;
GPIO_MAP1_BRR;
GPIO_MAP1_LCKR;
uint32_t :32;
GPIO_MAP1_AFRL;
GPIO_MAP1_AFRH;
uint32_t :64;
};
struct
{
uint32_t :192;
GPIO_MAP2_BSRRL;
GPIO_MAP2_BSRRH;
uint32_t :160;
};
};
Onde tudo GPIO_MAPx_YYY
é uma macro, definida como uint32_t :32
ou o tipo de registro (uma estrutura dedicada).
Aqui você vê o uint32_t :192;
que funciona bem, mas dispara um aviso.
O que eu considerei até agora:
Eu posso ter substituído por vários uint32_t :32;
(6 aqui), mas tenho alguns casos extremos em que o fiz uint32_t :1344;
(42) (entre outros). Portanto, prefiro não adicionar cerca de cem linhas em cima de outras 8k, embora a geração da estrutura seja feita por script.
A mensagem de aviso exata é algo como:
width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type
(Eu adoro o quão sombrio é).
Prefiro não resolver isso simplesmente removendo o aviso, mas usando
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop
pode ser uma solução ... se eu encontrar TheRightFlag
. No entanto, como apontado neste tópico , gcc/cp/class.c
com esta triste parte do código:
warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);
O que nos diz que não há -Wxxx
sinalização para remover este aviso ...
fonte
char unused[12];
e assim por diante?uint32_t :192;
.:42*32
vez de:1344
Respostas:
Use vários campos de bits anônimos adjacentes. Então, em vez de:
por exemplo, você teria:
Um para cada registro que você deseja que seja anônimo.
Se você tiver grandes espaços para preencher, pode ser mais claro e menos sujeito a erros usar macros para repetir o único espaço de 32 bits. Por exemplo, dado:
Em seguida, um espaço de 1344 (42 * 32 bits) pode ser adicionado assim:
fonte
uint32_t :1344;
está no local), então prefiro não ter que ir por aqui ...Que tal uma maneira C ++ ish?
Você obtém autocompletar por causa do
GPIO
namespace e não há necessidade de preenchimento fictício. Mesmo, é mais claro o que está acontecendo, como você pode ver o endereço de cada registrador, você não precisa confiar no comportamento de preenchimento do compilador.fonte
ldr r0, [r4, #16]
, enquanto os compiladores são mais propensos a perder essa otimização com cada endereço declarado separadamente. O GCC provavelmente carregará cada endereço GPIO em um registro separado. (De um pool literal, embora alguns deles possam ser representados como imediatos girados na codificação Thumb.)static volatile uint32_t &MAP0_MODER
, nãoinline
. Umainline
variável não compila. (static
evita ter qualquer armazenamento estático para o ponteiro, evolatile
é exatamente o que você deseja para o MMIO para evitar a eliminação do armazenamento morto ou a otimização da gravação / releitura.)static
funciona igualmente bem neste caso. Obrigado por mencionarvolatile
, vou adicioná-lo à minha resposta (e mudar inline para estático, para que funcione para pré C ++ 17).GPIOA::togglePin()
.Na arena de sistemas embarcados, você pode modelar hardware usando uma estrutura ou definindo ponteiros para os endereços de registro.
A modelagem por estrutura não é recomendada porque o compilador pode adicionar preenchimento entre os membros para fins de alinhamento (embora muitos compiladores para sistemas embarcados tenham um pragma para empacotar a estrutura).
Exemplo:
Você também pode usar a notação de matriz:
Se você deve usar a estrutura, IMHO, o melhor método para pular endereços seria definir um membro e não acessá-lo:
Em um de nossos projetos, temos constantes e estruturas de diferentes fornecedores (o fornecedor 1 usa constantes enquanto o fornecedor 2 usa estruturas).
fonte
static
membros de endereço acima de uma estrutura, assumindo que o preenchimento automático é capaz de mostrar membros estáticos. Caso contrário, também podem ser funções de membro embutidas.public:
eprivate:
como muitas vezes quiser, para obter a ordem correta dos campos.)public
e ambosprivate
, não será um tipo de layout padrão , portanto, não fornece as garantias de ordenação nas quais você está pensando. (E tenho quase certeza de que o caso de uso do OP requer um tipo de layout padrão.)volatile
dessas declarações, BTW, para registradores de E / S mapeados em memória.geza está certo que você realmente não quer usar classes para isso.
Mas, se você insistir, a melhor maneira de adicionar um membro não utilizado de largura de n bytes é simplesmente fazer isso:
Se você adicionar um pragma específico da implementação para evitar a adição de preenchimento arbitrário aos membros da classe, isso pode funcionar.
Para GNU C / C ++ (gcc, clang e outros que suportam as mesmas extensões), um dos locais válidos para colocar o atributo é:
(exemplo no explorador do compilador Godbolt mostrando
offsetof(GPIO, b)
= 7 bytes).fonte
Para expandir as respostas de @ Clifford e @Adam Kotwasinski:
fonte
Para expandir a resposta de Clifford, você sempre pode eliminar os campos de bits anônimos em macro.
Então, em vez de
usar
E então use como
Infelizmente, você precisará de tantas
EMPTY_32_X
variantes quantos bytes tiver :( Ainda assim, ele permite que você tenha declarações únicas em sua estrutura.fonte
#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1
e#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1
etc.Para definir um grande espaçador como grupos de 32 bits.
fonte
Acho que seria benéfico introduzir mais estrutura; o que pode, por sua vez, resolver o problema dos espaçadores.
Nomeie as variantes
Embora namespaces simples sejam bons, o problema é que você acaba com uma coleção heterogênea de campos e nenhuma maneira simples de passar todos os campos relacionados juntos. Além disso, ao usar estruturas anônimas em uma união anônima, você não pode passar referências às próprias estruturas ou usá-las como parâmetros de modelo.
Como uma primeira etapa, eu consideraria, portanto, separar o
struct
:E, finalmente, o cabeçalho global:
Agora, posso escrever um
void special_map0(Gpio:: Map0 volatile& map);
, bem como obter uma visão geral rápida de todas as arquiteturas disponíveis.Espaçadores Simples
Com a definição dividida em vários cabeçalhos, os cabeçalhos são individualmente muito mais gerenciáveis.
Portanto, minha abordagem inicial para atender exatamente aos seus requisitos seria continuar repetindo
std::uint32_t:32;
. Sim, ele adiciona algumas linhas 100s às 8k linhas existentes, mas como cada cabeçalho é individualmente menor, pode não ser tão ruim.Se você está disposto a considerar soluções mais exóticas, entretanto ...
Apresentando $.
É um fato pouco conhecido que
$
é um caractere viável para identificadores C ++; é até mesmo um personagem inicial viável (ao contrário dos dígitos).Uma
$
aparição no código-fonte provavelmente levantaria sobrancelhas e$$$$
definitivamente atrairá a atenção durante as revisões de código. Isso é algo que você pode facilmente aproveitar:Você pode até mesmo montar um "lint" simples como um gancho de pré-confirmação ou em seu CI que procura
$$$$
no código C ++ confirmado e rejeita tais confirmações.fonte
GPIO_MAP0_MODER
é presumivelmentevolatile
.) Possivelmente, referência ou uso de parâmetro de modelo de membros anteriormente anônimos pode ser útil. E para o caso geral de estruturas de enchimento, com certeza. Mas o caso de uso explica por que o OP os deixou anônimos.$$$padding##Index_[N_];
para tornar o nome do campo mais autoexplicativo caso ele tenha surgido no preenchimento automático ou durante a depuração. (Ouzz$$$padding
para classificá-lo apósGPIO...
nomes, porque todo o objetivo deste exercício de acordo com o OP é o preenchimento automático mais agradável para nomes de localização de E / S mapeados em memória.)volatile
qualificador na referência, porém, que foi corrigido. Quanto à nomeação; Vou deixar isso para o OP. Existem muitas variações (preenchimento, reservado, ...) e até mesmo o "melhor" prefixo para preenchimento automático pode depender do IDE em mãos, embora eu aprecie a ideia de ajustar a classificação.struct
) e queunion
acabam sendo propagados em todos os lugares, mesmo em bits específicos da arquitetura que poderia se importar menos com os outros.Embora eu concorde que structs não devem ser usados para acesso à porta de E / S de MCU, a pergunta original pode ser respondida desta forma:
Pode ser necessário substituir
__attribute__((packed))
por#pragma pack
ou semelhante, dependendo da sintaxe do compilador.A mistura de membros públicos e privados em uma estrutura normalmente resulta em que o layout de memória não seja mais garantido pelo padrão C ++. No entanto, se todos os membros não estáticos de uma estrutura forem privados, ela ainda será considerada POD / layout padrão e, portanto, as estruturas que os incorporam.
Por alguma razão, o gcc produz um aviso se um membro de uma estrutura anônima for privado, então eu tive que dar um nome a ele. Como alternativa, envolvê-lo em outra estrutura anônima também elimina o aviso (pode ser um bug).
Observe que o próprio
spacer
membro não é privado, portanto, os dados ainda podem ser acessados desta forma:No entanto, essa expressão parece um hack óbvio e, com sorte, não seria usada sem um bom motivo, muito menos como um erro.
fonte
Anti-solução.
NÃO FAÇA ISSO: Misture campos públicos e privados.
Talvez uma macro com um contador para gerar nomes de variáveis únicos seja útil?
fonte