as abordagens modulares são bastante úteis em geral (portáteis e limpas), por isso tento programar os módulos o mais independente possível de qualquer outro módulo. A maioria das minhas abordagens é baseada em uma estrutura que descreve o próprio módulo. Uma função de inicialização define os parâmetros principais, depois um manipulador (ponteiro para estrutura descritiva) é passado para qualquer função dentro do módulo que é chamada.
No momento, estou imaginando qual pode ser a melhor abordagem de memória de alocação para a estrutura que descreve um módulo. Se possível, eu gostaria do seguinte:
- Estrutura opaca, portanto, a estrutura só pode ser alterada pelo uso das funções de interface fornecidas
- Várias instâncias
- memória alocada pelo vinculador
Vejo as seguintes possibilidades, que todas conflitam com um dos meus objetivos:
declaração global
várias instâncias, todas citadas pelo vinculador, mas struct não é opaco
(#includes)
module_struct module;
void main(){
module_init(&module);
}
malloc
estrutura opaca, várias instâncias, mas allcotion no heap
no module.h:
typedef module_struct Module;
na função module.c init, malloc e retorne o ponteiro para a memória alocada
module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;
em main.c
(#includes)
Module *module;
void main(){
module = module_init();
}
declaração no módulo
estrutura opaca, alocada pelo vinculador, apenas um número predefinido de instâncias
mantenha toda a estrutura e memória internas ao módulo e nunca exponha um manipulador ou estrutura.
(#includes)
void main(){
module_init(_no_param_or_index_if_multiple_instances_possible_);
}
Existe uma opção para combiná-los de alguma forma para estrutura opaca, vinculador em vez de alocação de heap e várias / várias instâncias?
solução
como proposto em algumas respostas abaixo, acho que a melhor maneira é:
- reservar espaço para os módulos MODULE_MAX_INSTANCE_COUNT no arquivo de origem dos módulos
- não defina MODULE_MAX_INSTANCE_COUNT no próprio módulo
- adicione um #ifndef MODULE_MAX_INSTANCE_COUNT #error ao arquivo de cabeçalho dos módulos para garantir que o usuário dos módulos esteja ciente dessa limitação e defina o número máximo de instâncias desejadas para o aplicativo
- na inicialização de uma instância, retorne o endereço de memória (* void) da estrutura dessecetiva ou o índice de módulos (o que você quiser mais)
Respostas:
Claro que existe. Primeiro, no entanto, reconheça que "qualquer número" de instâncias deve ser corrigido, ou pelo menos um limite superior estabelecido, em tempo de compilação. Esse é um pré-requisito para que as instâncias sejam alocadas estaticamente (o que você está chamando de "alocação de vinculador"). Você pode ajustar o número sem modificação da fonte declarando uma macro que o especifique.
Em seguida, o arquivo de origem que contém a declaração de estrutura real e todas as suas funções associadas também declara uma matriz de instâncias com ligação interna. Ele fornece uma matriz, com ligação externa, de ponteiros para as instâncias ou uma função para acessar os vários ponteiros por índice. A variação da função é um pouco mais modular:
module.c
Eu acho que você já está familiarizado com como o cabeçalho declararia a estrutura como um tipo incompleto e declararia todas as funções (escritas em termos de ponteiros para esse tipo). Por exemplo:
module.h
Agora
struct module
é opaco em diferentes unidades de traduçãomodule.c
, * e você pode acessar e usar até o número de instâncias definidas em tempo de compilação sem qualquer alocação dinâmica.* A menos que você copie sua definição, é claro. O ponto é que
module.h
não faz isso.fonte
Eu programo pequenos microcontroladores em C ++, que conseguem exatamente o que você deseja.
O que você chama de módulo é uma classe C ++, que pode conter dados (acessíveis externamente ou não) e funções (da mesma forma). O construtor (uma função dedicada) a inicializa. O construtor pode usar parâmetros de tempo de execução ou (meu favorito) parâmetros de tempo de compilação (modelo). As funções na classe obtêm implicitamente a variável da classe como primeiro parâmetro. (Ou, geralmente, minha preferência, a classe pode atuar como um singleton oculto, para que todos os dados sejam acessados sem essa sobrecarga).
O objeto de classe pode ser global (para que você saiba no momento do link que tudo se encaixará) ou local da pilha, presumivelmente no principal. (Não gosto de C ++ globais devido à ordem de inicialização global indefinida, portanto prefiro o local da pilha).
Meu estilo de programação preferido é que os módulos são classes estáticas e sua configuração (estática) é feita por parâmetros de modelo. Isso evita quase tudo e permite a otimização. Combine isso com uma ferramenta que calcula o tamanho da pilha e você pode dormir sem preocupações :)
Minha palestra sobre essa maneira de codificar em C ++: Objects? Não, obrigado!
Muitos programadores incorporados / microcontroladores parecem não gostar do C ++ porque pensam que isso os forçaria a usar todo o C ++. Isso absolutamente não é necessário e seria uma péssima idéia. (Você provavelmente também não usa todo o C! Pense em heap, ponto flutuante, setjmp / longjmp, printf, ...)
Em um comentário, Adam Haun menciona RAII e inicialização. O IMO RAII tem mais a ver com a desconstrução, mas o ponto dele é válido: objetos globais serão construídos antes do início de seu principal, portanto, eles podem trabalhar em suposições inválidas (como a velocidade do relógio principal que será alterada posteriormente). Essa é mais uma razão para NÃO usar objetos inicializados por código global. (Eu uso um script vinculador que falhará quando eu tiver objetos globais inicializados por código.) Na IMO, esses 'objetos' devem ser explicitamente criados e transmitidos. Isso inclui um objeto 'recurso' em espera 'que fornece uma função wait (). Na minha configuração, este é 'objeto' que define a velocidade do clock do chip.
Falando sobre RAII: esse é mais um recurso do C ++ que é muito útil em pequenos sistemas embarcados, embora não seja o motivo (desalocação de memória) mais usado em sistemas mais amplos (os pequenos sistemas embarcados geralmente não usam desalocação dinâmica de memória). Pense em bloquear um recurso: você pode tornar o recurso bloqueado um objeto de invólucro e restringir o acesso ao recurso para ser possível apenas através do invólucro de bloqueio. Quando o wrapper fica fora do escopo, o recurso é desbloqueado. Isso impede o acesso sem travar e torna muito mais improvável esquecer o desbloqueio. com alguma mágica (modelo), pode ser zero.
A pergunta original não mencionou C, daí a minha resposta centrada em C ++. Se realmente deve ser C ....
Você pode usar truques de macro: declarar suas estruturas publicamente, para que eles tenham um tipo e possam ser alocados globalmente, mas altere os nomes de seus componentes além da usabilidade, a menos que alguma macro seja definida de maneira diferente, como é o caso do arquivo .c do seu módulo. Para segurança extra, você pode usar o tempo de compilação na confusão.
Ou tenha uma versão pública da sua estrutura que não tenha nada útil e tenha a versão privada (com dados úteis) apenas no seu arquivo .c e afirme que eles são do mesmo tamanho. Um pouco de truques de criação de arquivo pode automatizar isso.
O comentário do @Lundins sobre programadores ruins (incorporados):
O tipo de programador que você descreve provavelmente faria uma bagunça em qualquer idioma. As macros (presentes em C e C ++) são uma maneira óbvia.
As ferramentas podem ajudar até certo ponto. Para meus alunos, solicito um script construído que especifique sem exceções, no-rtti e forneça um erro de vinculador quando o heap for usado ou houver globais globais inicializados por código. E especifica warning = error e habilita quase todos os avisos.
Encorajo o uso de modelos, mas, com o constexpr e os conceitos, a metaprogramação é cada vez menos necessária.
"programadores confusos do Arduino" Gostaria muito de substituir o estilo de programação do Arduino (fiação, replicação de código nas bibliotecas) por uma abordagem moderna de C ++, que pode ser mais fácil, mais segura e produzir código mais rápido e menor. Se eu tivesse tempo e poder ...
fonte
Acredito que o FreeRTOS (talvez outro sistema operacional?) Faça algo parecido com o que você está procurando, definindo 2 versões diferentes da estrutura.
O 'real', usado internamente pelas funções do sistema operacional, e o 'falso', que é do mesmo tamanho que o 'real', mas não possui nenhum membro útil (apenas vários
int dummy1
e similares).Somente a estrutura 'fake' é exposta fora do código do SO, e isso é usado para alocar memória para instâncias estáticas da estrutura.
Internamente, quando as funções no sistema operacional são chamadas, elas recebem o endereço da estrutura 'falsa' externa como um identificador, e isso é tipicamente convertido em ponteiro para uma estrutura 'real' para que as funções do sistema operacional possam fazer o que precisam. Faz.
fonte
Na minha opinião, isso não faz sentido. Você pode colocar um comentário lá, mas não adianta tentar escondê-lo ainda mais.
C nunca fornecerá um isolamento tão alto, mesmo se não houver declaração para a estrutura, será fácil substituí-la acidentalmente com, por exemplo, memcpy () incorreto ou estouro de buffer.
Em vez disso, apenas dê um nome à estrutura e confie nas outras pessoas para escrever um bom código também. Isso também facilitará a depuração quando a estrutura tiver um nome que você possa usar para se referir a ela.
fonte
Perguntas mais frequentes sobre SW puro são mais solicitadas em /programming/ .
O conceito de expor uma estrutura de tipo incompleto ao chamador, como você descreve, é freqüentemente chamado de "tipo opaco" ou "ponteiros opacos" - estrutura anônima significa algo totalmente diferente.
O problema é que o chamador não poderá alocar instâncias do objeto, apenas ponteiros para ele. Em um PC, você usaria
malloc
dentro dos objetos "construtor", mas o malloc é proibido nos sistemas incorporados.Então, o que você faz no incorporado é fornecer um pool de memória. Você tem uma quantidade limitada de RAM, portanto, restringir o número de objetos que podem ser criados geralmente não é um problema.
Consulte Alocação estática de tipos de dados opacos em SO.
fonte