Eu já vi algumas bibliotecas de microcontroladores e suas funções fazem uma coisa de cada vez. Por exemplo, algo como isto:
void setCLK()
{
// Code to set the clock
}
void setConfig()
{
// Code to set the config
}
void setSomethingElse()
{
// 1 line code to write something to a register.
}
Em seguida, vêm outras funções que usam esse código de 1 linha que contém uma função para outros fins. Por exemplo:
void initModule()
{
setCLK();
setConfig();
setSomethingElse();
}
Não tenho certeza, mas acredito que dessa maneira seria criar mais chamadas para saltos e sobrecarregar o empilhamento dos endereços de retorno toda vez que uma função é chamada ou encerrada. E isso tornaria o programa lento, certo?
Eu procurei e em todos os lugares eles dizem que a regra básica da programação é que uma função deve executar apenas uma tarefa.
Portanto, se eu escrever diretamente um módulo de função InitModule que acerte o relógio, adicione algumas configurações desejadas e faça outra coisa sem chamar funções. É uma péssima abordagem ao escrever software incorporado?
EDIT 2:
Parece que muitas pessoas entenderam essa pergunta como se eu estivesse tentando otimizar um programa. Não, não tenho intenção de fazer . Estou deixando o compilador fazer isso, porque sempre será (espero que não!) Melhor do que eu.
Todas as culpas em mim por escolher um exemplo que represente algum código de inicialização . A questão não tem a intenção de considerar chamadas de função feitas para a finalidade de inicialização. Minha pergunta é : dividir uma determinada tarefa em pequenas funções de várias linhas ( portanto, in-line está fora de questão ), executar dentro de um loop infinito tem alguma vantagem sobre escrever função longa sem nenhuma função aninhada?
Por favor, considere a legibilidade definida na resposta de @Jonk .
fonte
Respostas:
Indiscutivelmente, no seu exemplo, o desempenho não importaria, pois o código é executado apenas uma vez na inicialização.
Como regra geral, eu uso: escreva seu código o mais legível possível e só comece a otimizar se você perceber que seu compilador não está fazendo corretamente sua mágica.
O custo de uma chamada de função em um ISR pode ser o mesmo de uma chamada de função durante a inicialização em termos de armazenamento e tempo. No entanto, os requisitos de tempo durante esse ISR podem ser muito mais críticos.
Além disso, como já observado por outras pessoas, o custo (e o significado do 'custo') de uma chamada de função difere de acordo com a plataforma, o compilador, a configuração de otimização do compilador e os requisitos do aplicativo. Haverá uma enorme diferença entre um 8051 e um córtex-m7, e um marcapasso e um interruptor de luz.
fonte
Não há vantagem em que eu possa pensar (mas veja a nota do JasonS na parte inferior), agrupando uma linha de código como uma função ou sub-rotina. Exceto, talvez, que você possa nomear a função como "legível". Mas você também pode comentar a linha. E desde que agrupar uma linha de código em uma função custa memória de código, espaço de pilha e tempo de execução, parece-me que é principalmente contraproducente. Em uma situação de ensino? Isso pode fazer algum sentido. Mas isso depende da classe dos alunos, de sua preparação prévia, do currículo e do professor. Principalmente, acho que não é uma boa ideia. Mas essa é a minha opinião.
O que nos leva à linha de fundo. Sua ampla área de perguntas tem sido, há décadas, uma questão de algum debate e permanece até hoje uma questão de algum debate. Então, pelo menos enquanto eu leio sua pergunta, parece-me uma pergunta baseada em opinião (como você fez).
Poderia deixar de ser tão baseado em opiniões quanto fosse, se você fosse mais detalhado sobre a situação e descrevesse cuidadosamente os objetivos que considerava primários. Quanto melhor você definir suas ferramentas de medição, mais objetivas serão as respostas.
Em termos gerais, você deseja fazer o seguinte para qualquer codificação. (Abaixo, assumirei que estamos comparando abordagens diferentes, todas as quais atingem os objetivos. Obviamente, qualquer código que falha ao executar as tarefas necessárias é pior do que o código que obtém êxito, independentemente de como ele seja escrito.)
O acima descrito é geralmente verdade sobre toda a codificação. Não discuti o uso de parâmetros, variáveis globais locais ou estáticas, etc. O motivo é que, para a programação incorporada, o espaço do aplicativo geralmente coloca novas restrições extremas e muito significativas e é impossível discutir todos eles sem discutir todos os aplicativos incorporados. E isso não está acontecendo aqui, de qualquer maneira.
Essas restrições podem ser qualquer uma (e mais) delas:
E assim por diante. (O código de fiação para instrumentação médica essencial à vida também possui um mundo inteiro.)
O resultado aqui é que a codificação incorporada geralmente não é gratuita para todos, onde você pode codificar como faria em uma estação de trabalho. Muitas vezes, existem razões competitivas graves para uma grande variedade de restrições muito difíceis. E eles podem argumentar fortemente contra as respostas mais tradicionais e de ações .
Em relação à legibilidade, acho que o código é legível se for escrito de uma maneira consistente que eu possa aprender enquanto o leio. E onde não há uma tentativa deliberada de ofuscar o código. Realmente não é muito mais necessário.
O código legível pode ser bastante eficiente e pode atender a todos os requisitos acima mencionados. O principal é que você entenda completamente o que cada linha de código que você escreve produz no nível da montagem ou da máquina, conforme você a codifica. O C ++ coloca uma carga séria no programador aqui, porque há muitas situações em que trechos idênticos de código C ++ realmente geram trechos diferentes de código de máquina com desempenhos muito diferentes. Mas C, geralmente, é principalmente uma linguagem "o que você vê é o que recebe". Portanto, é mais seguro nesse sentido.
EDIT por JasonS:
Uso C desde 1978 e C ++ desde 1987 e tenho muita experiência usando ambos para mainframes, minicomputadores e (principalmente) aplicativos incorporados.
Jason traz um comentário sobre o uso de 'inline' como um modificador. (Na minha perspectiva, esse é um recurso relativamente "novo", porque ele simplesmente não existiu por talvez metade da minha vida ou mais usando C e C ++.) O uso de funções em linha pode realmente fazer essas chamadas (mesmo para uma linha de código) bastante prático. E é muito melhor, sempre que possível, do que usar uma macro devido à digitação que o compilador pode aplicar.
Mas existem limitações também. A primeira é que você não pode confiar no compilador para "entender a dica". Pode ser que sim ou que não. E há boas razões para não dar a dica. (Para um exemplo óbvio, se o endereço da função for utilizado, isso requer a instanciação da função e o uso do endereço para fazer a chamada exigirá ... uma chamada. O código não pode ser incorporado então.) outras razões também. Os compiladores podem ter uma ampla variedade de critérios pelos quais julgam como lidar com a dica. E como programador, isso significa que você devegaste algum tempo aprendendo sobre esse aspecto do compilador; caso contrário, é provável que você tome decisões com base em idéias defeituosas. Portanto, isso adiciona um ônus ao escritor do código e também a qualquer leitor e também a quem planeja portar o código para outro compilador.
Além disso, os compiladores C e C ++ oferecem suporte à compilação separada. Isso significa que eles podem compilar uma parte do código C ou C ++ sem compilar nenhum outro código relacionado para o projeto. Para codificar em linha, supondo que o compilador possa optar por fazê-lo, ele não apenas deve ter a declaração "no escopo", mas também deve ter a definição. Geralmente, os programadores trabalharão para garantir que este seja o caso se eles estiverem usando 'inline'. Mas é fácil os erros aparecerem.
Em geral, embora eu também use inline onde achar apropriado, eu suponho que não posso confiar nela. Se o desempenho é um requisito significativo, e acho que o OP já escreveu claramente que houve um impacto significativo no desempenho quando foram para uma rota mais "funcional", então eu certamente escolheria evitar confiar na linha como prática de codificação e em vez disso, seguiria um padrão ligeiramente diferente, mas inteiramente consistente, de escrever código.
Uma observação final sobre 'inline' e definições sendo "dentro do escopo" para uma etapa de compilação separada. É possível (nem sempre confiável) que o trabalho seja realizado no estágio de vinculação. Isso pode ocorrer se, e somente se, um compilador C / C ++ enterrar detalhes suficientes nos arquivos de objeto para permitir que um vinculador atue em solicitações "em linha". Pessoalmente, não experimentei um sistema de vinculação (fora da Microsoft) que suporta esse recurso. Mas isso pode ocorrer. Novamente, se deve ou não confiar, dependerá das circunstâncias. Mas geralmente presumo que isso não tenha sido colocado no linker, a menos que eu saiba de outra forma com base em boas evidências. E se eu confiar nisso, será documentado em um lugar de destaque.
C ++
Para os interessados, aqui está um exemplo de por que permaneço bastante cauteloso em C ++ ao codificar aplicativos incorporados, apesar de sua pronta disponibilidade hoje. Vou descartar alguns termos que acho que todos os programadores C ++ incorporados precisam conhecer a frio :
Essa é apenas uma pequena lista. Se você ainda não sabe tudo sobre esses termos e por que os listei (e muitos mais não listei aqui), desaconselho o uso de C ++ para trabalho incorporado, a menos que não seja uma opção para o projeto .
Vamos dar uma olhada rápida na semântica de exceção do C ++ para obter apenas uma amostra.
O compilador C ++ vê a primeira chamada para foo () e pode apenas permitir que um quadro de ativação normal ocorra, se foo () lançar uma exceção. Em outras palavras, o compilador C ++ sabe que nenhum código extra é necessário neste momento para dar suporte ao processo de desenrolamento de quadros envolvido no tratamento de exceções.
Porém, depois que a String s é criada, o compilador C ++ sabe que deve ser destruído adequadamente antes que um desenrolamento de quadros possa ser permitido, se uma exceção ocorrer posteriormente. Portanto, a segunda chamada para foo () é semanticamente diferente da primeira. Se a segunda chamada para foo () gerar uma exceção (o que pode ou não ser feito), o compilador deve ter colocado um código projetado para lidar com a destruição de String s antes de permitir que o quadro normal ocorra. Isso é diferente do código necessário para a primeira chamada para foo ().
(É possível adicionar decorações adicionais em C ++ para ajudar a limitar esse problema. Mas o fato é que os programadores que usam C ++ precisam estar muito mais cientes das implicações de cada linha de código que escrevem.)
Diferentemente do malloc de C, o novo C ++ usa exceções para sinalizar quando não é possível executar a alocação de memória bruta. Então será 'dynamic_cast'. (Consulte a 3ª edição da Stroustrup, The C ++ Programming Language, páginas 384 e 385 para obter as exceções padrão em C ++.) Os compiladores podem permitir que esse comportamento seja desabilitado. Mas, em geral, você terá uma sobrecarga devido a prólogos e epílogos de manipulação de exceção adequadamente formados no código gerado, mesmo quando as exceções realmente não ocorrerem e mesmo quando a função que está sendo compilada não tiver realmente nenhum bloco de manipulação de exceção. (Stroustrup lamentou isso publicamente).
Sem a especialização parcial de modelos (nem todos os compiladores C ++ oferecem suporte), o uso de modelos pode significar desastre para a programação incorporada. Sem ele, o código bloom é um risco sério que pode matar um projeto incorporado de memória pequena rapidamente.
Quando uma função C ++ retorna um objeto, um compilador temporário sem nome é criado e destruído. Alguns compiladores C ++ podem fornecer código eficiente se um construtor de objetos for usado na instrução de retorno, em vez de um objeto local, reduzindo as necessidades de construção e destruição de um objeto. Mas nem todo compilador faz isso e muitos programadores de C ++ nem sequer estão cientes dessa "otimização do valor de retorno".
Fornecer um construtor de objeto com um único tipo de parâmetro pode permitir que o compilador C ++ encontre um caminho de conversão entre dois tipos de maneiras completamente inesperadas para o programador. Esse tipo de comportamento "inteligente" não faz parte do C.
Uma cláusula catch que especifica um tipo de base "fatiará" um objeto derivado lançado, porque o objeto lançado é copiado usando o "tipo estático" da cláusula catch e não o "tipo dinâmico" do objeto. Uma fonte incomum de miséria de exceção (quando você sente que pode pagar exceções no seu código incorporado).
Os compiladores C ++ podem gerar automaticamente construtores, destruidores, construtores de cópia e operadores de atribuição para você, com resultados indesejados. Leva tempo para obter facilidade com os detalhes disso.
Passar matrizes de objetos derivados para uma função que aceita matrizes de objetos base, raramente gera avisos do compilador, mas quase sempre gera comportamento incorreto.
Como o C ++ não invoca o destruidor de objetos parcialmente construídos quando ocorre uma exceção no construtor de objetos, o tratamento de exceções nos construtores geralmente exige "ponteiros inteligentes" para garantir que fragmentos construídos no construtor sejam destruídos corretamente se ocorrer uma exceção no local. . (Consulte Stroustrup, páginas 367 e 368.) Esse é um problema comum ao escrever boas classes em C ++, mas é claro evitado em C, pois C não possui a semântica de construção e destruição incorporada. Escrevendo código adequado para lidar com a construção de subobjetos em um objeto significa escrever código que deve lidar com esse problema semântico exclusivo em C ++; em outras palavras, "escrevendo em torno" de comportamentos semânticos do C ++.
C ++ pode copiar objetos passados para parâmetros de objeto. Por exemplo, nos seguintes fragmentos, a chamada "rA (x);" pode fazer com que o compilador C ++ chame um construtor para o parâmetro p, a fim de chamar o construtor de cópia para transferir o objeto x para o parâmetro p, então outro construtor para o objeto de retorno (um temporário sem nome) da função rA, que obviamente é copiado do parâmetro p. Pior ainda, se a classe A tiver seus próprios objetos que precisam de construção, isso pode ser causado por um telescópio desastroso. (O programador CA evitaria a maior parte desse lixo, otimizando manualmente, pois os programadores C não possuem uma sintaxe tão prática e precisam expressar todos os detalhes, um de cada vez.)
Finalmente, uma breve nota para programadores C. longjmp () não possui um comportamento portátil em C ++. (Alguns programadores de C usam isso como um tipo de mecanismo de "exceção".) Alguns compiladores de C ++ realmente tentam configurar as coisas para limpar quando o longjmp é obtido, mas esse comportamento não é portátil no C ++. Se o compilador limpar objetos construídos, ele não será portátil. Se o compilador não os limpar, os objetos não serão destruídos se o código deixar o escopo dos objetos construídos como resultado do longjmp e o comportamento for inválido. (Se o uso de longjmp em foo () não deixar um escopo, o comportamento poderá ser bom.) Isso não é muito usado pelos programadores incorporados em C, mas eles devem se conscientizar desses problemas antes de usá-los.
fonte
inline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }
que é chamado em vários lugares é um candidato perfeito.1) Código de legibilidade e manutenção primeiro. O aspecto mais importante de qualquer base de código é que ela é bem estruturada. Um software bem escrito tende a ter menos erros. Pode ser necessário fazer alterações em algumas semanas / meses / anos, e isso ajuda imensamente se o seu código for de boa leitura. Ou talvez alguém precise fazer uma mudança.
2) O desempenho do código executado uma vez não importa muito. Cuidado com o estilo, não com o desempenho
3) Mesmo o código em loops apertados precisa estar correto em primeiro lugar. Se você enfrentar problemas de desempenho, otimize quando o código estiver correto.
4) Se você precisa otimizar, precisa medir! Não importa se você pensa ou alguém lhe diz que isso
static inline
é apenas uma recomendação para o compilador. Você precisa dar uma olhada no que o compilador faz. Você também deve medir se o alinhamento melhorou o desempenho. Em sistemas embarcados, você também precisa medir o tamanho do código, pois a memória de código geralmente é bastante limitada. Esta é a regra mais importante que distingue engenharia de adivinhação. Se você não mediu, não ajudou. Engenharia está medindo. A ciência está anotando;)fonte
Quando uma função é chamada apenas em um local (mesmo dentro de outra função), o compilador sempre coloca o código nesse local, em vez de realmente chamar a função. Se a função é chamada em muitos lugares, faz sentido usar uma função pelo menos do ponto de vista do tamanho do código.
Após a compilação, o código não terá várias chamadas; a legibilidade será bastante aprimorada.
Além disso, você desejará ter, por exemplo, o código de inicialização do ADC na mesma biblioteca com outras funções do ADC que não estão no arquivo c principal.
Muitos compiladores permitem especificar diferentes níveis de otimização para velocidade ou tamanho do código; portanto, se você tiver uma pequena função chamada em muitos locais, a função será "inline", copiada para lá em vez de chamar.
A otimização da velocidade incorporará as funções em todos os lugares possíveis; a otimização para o tamanho do código chamará a função; no entanto, quando uma função for chamada apenas em um local, como no seu caso, ela será sempre "incorporada".
Código como este:
irá compilar para:
sem usar nenhuma ligação.
E a resposta à sua pergunta, no seu exemplo ou similar, a legibilidade do código não afeta o desempenho, nada é muito em velocidade ou tamanho do código. É comum usar várias chamadas apenas para tornar o código legível; no final, elas são cumpridas como um código embutido.
Atualize para especificar que as instruções acima não são válidas para compiladores de versão grátis com defeito, como a versão gratuita Microchip XCxx. Esse tipo de chamada de função é uma mina de ouro para o Microchip mostrar quanto melhor é a versão paga e, se você compilar isso, encontrará no ASM exatamente o número de chamadas que possui no código C.
Também não é para programadores burros que esperam usar o ponteiro para uma função embutida.
Esta é a seção eletrônica, não o C C ++ geral ou a seção de programação. A questão é sobre a programação de microcontroladores em que qualquer compilador decente fará a otimização acima por padrão.
Portanto, pare de votar apenas porque, em casos raros e incomuns, isso pode não ser verdade.
fonte
Primeiro, não há melhor ou pior; é tudo uma questão de opinião. Você está muito certo de que isso é ineficiente. Pode ser otimizado ou não; depende. Normalmente você verá esses tipos de funções, relógio, GPIO, timer, etc. em arquivos / diretórios separados. Os compiladores geralmente não foram capazes de otimizar essas lacunas. Existe uma que eu conheço, mas que não é amplamente usada para coisas como essa.
Único arquivo:
Escolhendo um destino e um compilador para fins de demonstração.
Isto é o que a maioria das respostas aqui estão dizendo, que você é ingênuo e que tudo isso é otimizado e as funções são removidas. Bem, eles não são removidos, pois são definidos globalmente por padrão. Podemos removê-los se não for necessário fora deste arquivo.
remove-os agora como estão alinhados.
Mas a realidade é quando você compra bibliotecas de chips ou BSP,
Definitivamente, você começará a adicionar uma sobrecarga, que tem um custo perceptível para desempenho e espaço. Alguns a cinco por cento de cada um, dependendo do tamanho de cada função.
Por que isso é feito de qualquer maneira? Parte disso é o conjunto de regras que os professores ensinariam ou ainda ensinam para facilitar o código de classificação. As funções devem caber em uma página (quando você imprimiu seu trabalho em papel), não faça isso, não faça isso, etc. Muito disso é criar bibliotecas com nomes comuns para diferentes destinos. Se você tem dezenas de famílias de microcontroladores, algumas das quais compartilham periféricos e outras não, talvez três ou quatro tipos diferentes de UART misturados entre as famílias, GPIOs diferentes, controladores SPI etc. Você pode ter uma função genérica gpio_init (), get_timer_count (), etc. E reutilize essas abstrações para os diferentes periféricos.
Torna-se um caso de manutenção e design de software, com alguma legibilidade possível. Manutenção, legibilidade e desempenho que você não pode ter tudo; você pode escolher apenas um ou dois de cada vez, não os três.
Essa é uma pergunta muito baseada em opiniões, e o exposto acima mostra as três principais maneiras pelas quais isso pode acontecer. Quanto a qual caminho é MELHOR, é estritamente opinião. Está fazendo todo o trabalho em uma função? Uma pergunta baseada em opinião, algumas pessoas preferem desempenho, outras definem modularidade e sua versão de legibilidade como BEST. A questão interessante com o que muitas pessoas chamam de legibilidade é extremamente dolorosa; Para "ver" o código, é necessário ter 50 a 10.000 arquivos abertos de uma só vez e, de alguma forma, tentar ver linearmente as funções em ordem de execução para ver o que está acontecendo. Acho que é o oposto da legibilidade, mas outros acham legível à medida que cada item se encaixa na tela / janela do editor e pode ser consumido inteiro depois de memorizarem as funções que estão sendo chamadas e / ou de um editor que possa entrar e sair da cada função dentro de um projeto.
Esse é outro grande fator quando você vê várias soluções. Editores de texto, IDEs etc. são muito pessoais e vão além do vi vs Emacs. Eficiência de programação, as linhas por dia / mês aumentam se você estiver confortável e eficiente com a ferramenta que está usando. Os recursos da ferramenta podem / vão intencionalmente ou não se inclinam para a forma como os fãs dessa ferramenta escrevem código. E, como resultado, se um indivíduo está escrevendo essas bibliotecas, o projeto reflete até certo ponto esses hábitos. Mesmo que seja uma equipe, os hábitos / preferências do desenvolvedor líder ou do chefe podem ser forçados a permanecer no restante da equipe.
Padrões de codificação que têm muitas preferências pessoais enterradas neles, vi muito religioso vs. Emacs novamente, abas versus espaços, como os colchetes são alinhados, etc.
Como você deve escrever o seu? Como quiser, não há realmente uma resposta errada se funcionar. Existe um código ruim ou arriscado, mas se escrito para que você possa mantê-lo conforme necessário, ele atende aos seus objetivos de projeto, desiste da legibilidade e de alguma manutenção se o desempenho é importante ou vice-versa. Você gosta de nomes curtos de variáveis para que uma única linha de código caiba na largura da janela do editor? Ou nomes longos e excessivamente descritivos para evitar confusão, mas a legibilidade diminui porque você não pode obter uma linha em uma página; agora está visualmente quebrado, mexendo com o fluxo.
Você não vai bater um home run pela primeira vez no bastão. Pode / deve levar décadas para realmente definir seu estilo. Ao mesmo tempo, durante esse período, seu estilo pode mudar, inclinando-se para um lado por um tempo e depois para outro.
Você ouvirá muitas coisas que não otimizam, nunca otimizam e otimização prematura. Mas, como mostrado, projetos como este desde o início criam problemas de desempenho, então você começa a ver hacks para resolver esse problema, em vez de redesenhar desde o início para executar. Concordo que há situações, uma única função que algumas linhas de código podem ser difíceis de manipular o compilador com base no medo do que o compilador fará de outra forma (observe com experiência que esse tipo de codificação se torna fácil e natural, otimizando enquanto escreve, sabendo como o compilador compilará o código), então você deseja confirmar onde o ladrão de ciclo realmente está, antes de atacá-lo.
Você também precisa criar seu código para o usuário até certo ponto. Se este for seu projeto, você é o único desenvolvedor; é o que você quiser. Se você está tentando criar uma biblioteca para doar ou vender, provavelmente deseja que seu código pareça com todas as outras bibliotecas, centenas a milhares de arquivos com pequenas funções, nomes longos de funções e nomes longos de variáveis. Apesar dos problemas de legibilidade e desempenho, na IMO, você encontrará mais pessoas capazes de usar esse código.
fonte
Regra muito geral - o compilador pode otimizar melhor que você. Obviamente, há exceções se você estiver fazendo coisas muito intensas em loop, mas no geral, se desejar uma boa otimização para velocidade ou tamanho do código, escolha seu compilador com sabedoria.
fonte
Com certeza, depende do seu próprio estilo de codificação. Uma regra geral que existe, é que nomes de variáveis e nomes de funções devem ser tão claros e autoexplicativos quanto possível. Quanto mais sub-chamadas ou linhas de código você colocar em uma função, mais difícil será definir uma tarefa clara para essa função. No seu exemplo, você tem uma função
initModule()
que inicializa coisas e chama sub-rotinas, que depois ajustam o relógio ou definem a configuração . Você pode dizer isso apenas lendo o nome da função. Se você colocar todo o código das sub-rotinasinitModule()
diretamente, fica menos óbvio o que a função realmente faz. Mas, com frequência, é apenas uma diretriz.fonte
Se uma função realmente faz apenas uma coisa muito pequena, considere fazê-la
static inline
.Adicione-o a um arquivo de cabeçalho em vez do arquivo C e use as palavras
static inline
para defini-lo:Agora, se a função for um pouco mais longa, como ultrapassar 3 linhas, pode ser uma boa ideia evitar
static inline
e adicioná-la ao arquivo .c. Afinal, os sistemas embarcados têm memória limitada e você não deseja aumentar muito o tamanho do código.Além disso, se você definir a função
file1.c
e usá-lafile2.c
, o compilador não a embutirá automaticamente. No entanto, se você o definirfile1.h
como umastatic inline
função, é provável que o seu compilador o incline.Essas
static inline
funções são extremamente úteis na programação de alto desempenho. Descobri que eles aumentam o desempenho do código com frequência por um fator superior a três.fonte
file1.c
e usá-lafile2.c
, o compilador não a embutirá automaticamente. Falso . Veja, por exemplo,-flto
em gcc ou clang.Uma dificuldade em tentar escrever código eficiente e confiável para microcontroladores é que alguns compiladores não podem lidar com determinadas semânticas de maneira confiável, a menos que o código use diretivas específicas do compilador ou desative muitas otimizações.
Por exemplo, se tiver um sistema de núcleo único com uma rotina de serviço de interrupção [executada por uma marcação de timer ou o que seja]:
deve ser possível escrever funções para iniciar uma operação de gravação em segundo plano ou aguardar a conclusão:
e invoque esse código usando:
Infelizmente, com as otimizações completas ativadas, um compilador "inteligente" como gcc ou clang decidirá que não há como o primeiro conjunto de gravações ter efeito sobre o observável do programa e, portanto, pode ser otimizado. Compiladores de qualidade como
icc
esses são menos propensos a fazer isso se o ato de definir uma interrupção e aguardar a conclusão envolver gravações e leituras voláteis (como é o caso aqui), mas a plataforma visada poricc
não é tão popular para sistemas embarcados.O Padrão ignora deliberadamente questões de qualidade de implementação, considerando que existem várias maneiras razoáveis de lidar com a construção acima:
Uma implementação de qualidade destinada exclusivamente a campos como processamento de números de ponta poderia razoavelmente esperar que o código escrito para esses campos não contenha construções como as mencionadas acima.
Uma implementação de qualidade pode tratar todos os acessos a
volatile
objetos como se eles pudessem acionar ações que acessariam qualquer objeto visível para o mundo externo.Uma implementação simples, mas de qualidade decente, destinada ao uso de sistemas embarcados, pode tratar todas as chamadas para funções não marcadas como "inline" como se elas pudessem acessar qualquer objeto exposto ao mundo exterior, mesmo que não seja tratado
volatile
como descrito em # 2)A Norma não tenta sugerir quais das abordagens acima seriam mais apropriadas para uma implementação de qualidade, nem exigir que as implementações "em conformidade" sejam de qualidade suficientemente boa para serem utilizadas para qualquer finalidade específica. Consequentemente, alguns compiladores como o gcc ou o clang exigem efetivamente que qualquer código que queira usar esse padrão seja compilado com muitas otimizações desabilitadas.
Em alguns casos, garantir que as funções de E / S estejam em uma unidade de compilação separada e um compilador não terá escolha, mas supor que eles possam acessar qualquer subconjunto arbitrário de objetos expostos ao mundo exterior pode ser um mínimo razoável. A maneira mais perigosa de escrever código que funcione de maneira confiável com o gcc e o clang. Nesses casos, no entanto, o objetivo não é evitar o custo extra de uma chamada de função desnecessariamente, mas aceitar o custo que deveria ser desnecessário em troca da obtenção da semântica necessária.
fonte
volatile
acessos como se eles pudessem potencialmente desencadear acessos arbitrários a outros objetos), mas, por qualquer motivo, o gcc e o clang preferem tratar os problemas de qualidade de implementação como um convite para se comportar de maneira inútil.buff
não for declaradovolatile
, ele não será tratado como uma variável volátil, os acessos a ele poderão ser reordenados ou otimizados totalmente se aparentemente não forem usados posteriormente. A regra é simples: marque todas as variáveis que podem ser acessadas fora do fluxo normal do programa (como visto pelo compilador) comovolatile
. O conteúdo ébuff
acessado em um manipulador de interrupções? Sim. Então deveria servolatile
.magic_write_count
é zero, o armazenamento pertence à linha principal. Quando é diferente de zero, pertence ao manipulador de interrupções. Tornarbuff
volátil exigiria que todas as funções em qualquer lugar que operam com ele usemvolatile
ponteiros qualificados, o que prejudicaria a otimização muito mais do que ter um compilador ... #