Depois de alguns anos codificando em C ++, recentemente me ofereceram um emprego de codificador em C, na área de embarcados.
Deixando de lado a questão de saber se é certo ou errado descartar o C ++ no campo incorporado, há alguns recursos / expressões idiomáticas em C ++ dos quais eu sentiria muita falta. Apenas para citar alguns:
- Estruturas de dados genéricas e seguras de tipo (usando modelos).
- RAII. Especialmente em funções com vários pontos de retorno, por exemplo, não ter que se lembrar de liberar o mutex em cada ponto de retorno.
- Destruidores em geral. Ou seja, você escreve um d'tor uma vez para MyClass, então se uma instância de MyClass for membro de MyOtherClass, MyOtherClass não precisa desinicializar explicitamente a instância de MyClass - seu d'tor é chamado automaticamente.
- Namespaces.
Quais são suas experiências ao mudar de C ++ para C?
Quais substitutos de C você encontrou para seus recursos / idiomas favoritos de C ++? Você descobriu algum recurso C que gostaria que o C ++ tivesse?
Respostas:
Trabalhando em um projeto embarcado, tentei trabalhar em C uma vez, e simplesmente não aguentava. Era tão prolixo que tornava difícil ler qualquer coisa. Além disso, gostei dos contêineres otimizados para embutidos que escrevi, que tiveram que se tornar muito menos seguros e mais difíceis de corrigir
#define
.Código que em C ++ parecia:
if(uart[0]->Send(pktQueue.Top(), sizeof(Packet))) pktQueue.Dequeue(1);
torna-se em:
if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet))) Queue_Packet_Dequeue(pktQueue, 1);
o que muitas pessoas provavelmente dirão que está bom, mas fica ridículo se você tiver que fazer mais do que algumas chamadas de "método" em uma linha. Duas linhas de C ++ se transformariam em cinco de C (devido aos limites de comprimento de linha de 80 caracteres). Ambos gerariam o mesmo código, então não é como se o processador de destino se importasse!
Uma vez (em 1995), tentei escrever muito C para um programa de processamento de dados com multiprocessador. O tipo em que cada processador tem sua própria memória e programa. O compilador fornecido pelo fornecedor era um compilador C (algum tipo de derivado do HighC), suas bibliotecas eram de código fechado, então eu não poderia usar o GCC para construir e suas APIs foram projetadas com a ideia de que seus programas seriam principalmente a inicialização / processo / terminate, portanto a comunicação entre processadores era, na melhor das hipóteses, rudimentar.
Eu demorei cerca de um mês antes de desistir, encontrei uma cópia do cfront e a hackeei nos makefiles para que eu pudesse usar C ++. Cfront nem mesmo suportava templates, mas o código C ++ era muito, muito mais claro.
Estruturas de dados genéricas e seguras de tipo (usando modelos).
A coisa mais próxima que C tem de modelos é declarar um arquivo de cabeçalho com uma grande quantidade de código semelhante a:
TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { /* ... */ }
em seguida, puxe-o com algo como:
#define TYPE Packet #include "Queue.h" #undef TYPE
Observe que isso não funcionará para tipos compostos (por exemplo, sem filas de
unsigned char
) a menos que você faça umtypedef
primeiro.Ah, e lembre-se, se esse código não for realmente usado em nenhum lugar, você nem saberá se ele está sintaticamente correto.
EDIT: Mais uma coisa: você precisará gerenciar manualmente a instanciação do código. Se o seu código de "modelo" não for todas as funções embutidas, então você terá que colocar algum controle para se certificar de que as coisas sejam instanciadas apenas uma vez para que seu vinculador não cuspa uma pilha de erros de "várias instâncias de Foo" .
Para fazer isso, você terá que colocar o material não embutido em uma seção de "implementação" em seu arquivo de cabeçalho:
#ifdef implementation_##TYPE /* Non-inlines, "static members", global definitions, etc. go here. */ #endif
E então, em um só lugar em todo o seu código por variante de modelo , você deve:
#define TYPE Packet #define implementation_Packet #include "Queue.h" #undef TYPE
Além disso, esta seção de implementação precisa estar fora do padrão
#ifndef
/#define
/#endif
litany, porque você pode incluir o arquivo de cabeçalho de modelo em outro arquivo de cabeçalho, mas precisa instanciar depois em um.c
arquivo.Sim, fica feio rápido. É por isso que a maioria dos programadores C nem mesmo tenta.
RAII.
Especialmente em funções com vários pontos de retorno, por exemplo, não ter que se lembrar de liberar o mutex em cada ponto de retorno.
Bem, esqueça o seu código bonito e se acostume com todos os seus pontos de retorno (exceto o final da função) sendo
goto
s:TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this) { TYPE * result; Mutex_Lock(this->lock); if(this->head == this->tail) { result = 0; goto Queue_##TYPE##_Top_exit:; } /* Figure out `result` for real, then fall through to... */ Queue_##TYPE##_Top_exit: Mutex_Lock(this->lock); return result; }
Destruidores em geral.
Ou seja, você escreve um d'tor uma vez para MyClass, se uma instância MyClass for membro de MyOtherClass, MyOtherClass não precisa desinicializar explicitamente a instância MyClass - seu d'tor é chamado automaticamente.
A construção de objetos deve ser explicitamente tratada da mesma maneira.
Namespaces.
Na verdade, isso é simples de corrigir: basta adicionar um prefixo a cada símbolo. Esta é a principal causa do inchaço da fonte de que falei anteriormente (uma vez que as classes são namespaces implícitos). O pessoal de C tem vivido isso, bem, desde sempre, e provavelmente não verá qual é o problema.
YMMV
fonte
AT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
BOARD_OSCOUNT
(que é o valor do limite de tempo para espera de um relógio para interruptor,? Claro, huh) é realmente um#define
noboard.h
. Há também, nessa mesma função, muito código de loop de rotação copiado e colado, que deveria ter sido transformado em duas linhas#define
(e, quando fiz exatamente isso, ele salvou alguns bytes de código e fez o função mais legível, tornando os conjuntos de registradores e os loops de rotação mais distintos). Um dos grandes motivos para usar C é que ele permite microgerenciar e otimizar tudo, mas a maioria dos códigos que vi não incomoda.Mudei de C ++ para C por um motivo diferente (algum tipo de reação alérgica;) e há apenas algumas coisas que perdi e algumas coisas que ganhei. Se você se limitar a C99, se puder, existem construções que permitem que você programe de forma muito agradável e segura, em particular
for
A variável de escopo pode ajudá-lo a fazer o gerenciamento de recursos vinculados ao escopo , em particular para garantir aunlock
mutexes oufree
arrays, mesmo sob retornos de função preliminares__VA_ARGS__
macros podem ser usadas para ter argumentos padrão para funções e para fazer o desenrolamento de códigoinline
funções e macros que combinam bem para substituir (meio que) funções sobrecarregadasfonte
for
escopos, você chegará ao P99, onde também poderá procurar exemplos e descrições das outras partes.void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }
) e inicialização de estrutura por nomes de campo (por exemplostruct Foo bar = { .field1 = 5, .field2 = 10 };
), este último que eu adoraria ver em C ++, especialmente com objetos não POD (por exemploUART uart[2] = { UART(0x378), UART(0x278) };
) .Nada como o STL existe para C.
Existem libs disponíveis que fornecem funcionalidade semelhante, mas não é mais embutido.
Acho que seria um dos meus maiores problemas ... Saber com qual ferramenta poderia resolver o problema, mas não ter as ferramentas disponíveis no idioma que devo usar.
fonte
A diferença entre C e C ++ é a previsibilidade do comportamento do código.
É mais fácil prever com grande precisão o que seu código fará em C; em C ++, pode se tornar um pouco mais difícil chegar a uma previsão exata.
A previsibilidade em C dá a você um controle melhor do que seu código está fazendo, mas isso também significa que você precisa fazer mais coisas.
Em C ++, você pode escrever menos código para fazer a mesma coisa, mas (pelo menos para mim), ocasionalmente, tenho problemas para saber como o código do objeto está disposto na memória e seu comportamento esperado.
fonte
-s
sinalizadorgcc
para obter o despejo do assembly, procuro a função de interesse e começo a ler. É uma ótima maneira de aprender as peculiaridades de qualquer linguagem compilada.Na minha linha de trabalho - que está embutida, aliás - estou constantemente alternando entre C e C ++.
Quando estou em C, sinto falta de C ++:
modelos (incluindo, mas não se limitando a contêineres STL). Eu os uso para coisas como contadores especiais, pools de buffer, etc. (construí minha própria biblioteca de modelos de classe e modelos de função que uso em diferentes projetos incorporados)
biblioteca padrão muito poderosa
destruidores, que obviamente tornam o RAII possível (mutexes, desativação de interrupção, rastreamento, etc.)
especificadores de acesso, para melhor fiscalizar quem pode usar (não ver) o que
Eu uso herança em projetos maiores, e o suporte embutido do C ++ para isso é muito mais limpo e agradável do que o "hack" C de embutir a classe base como o primeiro membro (sem mencionar a invocação automática de construtores, listas de inicialização, etc. ), mas os itens listados acima são os que eu mais sinto falta.
Além disso, provavelmente apenas cerca de um terço dos projetos C ++ incorporados em que trabalho usam exceções, então me acostumei a viver sem elas, então não sinto muita falta delas quando volto para C.
Por outro lado, quando volto para um projeto C com um número significativo de desenvolvedores, há classes inteiras de problemas C ++ que estou acostumada a explicar para as pessoas e que desaparecem. Principalmente problemas devido à complexidade do C ++ e pessoas que pensam que sabem o que está acontecendo, mas estão realmente na parte "C com classes" da curva de confiança do C ++ .
Se pudesse escolher, eu preferiria usar C ++ em um projeto, mas apenas se a equipe for bastante sólida na linguagem. Também, claro, assumindo que não é um projeto de 8 K μC em que estou efetivamente escrevendo "C" de qualquer maneira.
fonte
Algumas observações
fonte
Praticamente os mesmos motivos que tenho para usar C ++ ou uma mistura de C / C ++ em vez de C. puro. Posso viver sem namespaces, mas os uso o tempo todo, se o padrão de código permitir. O motivo é que você pode escrever um código muito mais compacto em C ++. Isso é muito útil para mim, eu escrevo servidores em C ++ que tendem a travar de vez em quando. Nesse ponto, ajuda muito se o código que você está examinando for curto e consistente. Por exemplo, considere o seguinte código:
uint32_t ScoreList::FindHighScore( uint32_t p_PlayerId) { MutexLock lock(m_Lock); uint32_t highScore = 0; for(int i = 0; i < m_Players.Size(); i++) { Player& player = m_Players[i]; if(player.m_Score > highScore) highScore = player.m_Score; } return highScore; }
Em C, isso se parece com:
uint32_t ScoreList_getHighScore( ScoreList* p_ScoreList) { uint32_t highScore = 0; Mutex_Lock(p_ScoreList->m_Lock); for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++) { Player* player = p_ScoreList->m_Players[i]; if(player->m_Score > highScore) highScore = player->m_Score; } Mutex_UnLock(p_ScoreList->m_Lock); return highScore; }
Não é um mundo de diferença. Mais uma linha de código, mas isso tende a se somar. Normalmente você tenta o seu melhor para mantê-lo limpo e enxuto, mas às vezes você tem que fazer algo mais complexo. E nessas situações você valoriza sua contagem de linhas. Mais uma linha é mais uma coisa a ser observada quando você tenta descobrir por que sua rede de transmissão para repentinamente de entregar mensagens.
De qualquer forma, acho que C ++ me permite fazer coisas mais complexas de maneira segura.
fonte
Acho que o principal problema do c ++ ser mais difícil de ser aceito em ambiente embarcado é a falta de engenheiros que entendam como usar o c ++ corretamente.
Sim, o mesmo raciocínio pode ser aplicado a C também, mas felizmente não existem muitas armadilhas em C que podem atirar no próprio pé. C ++ por outro lado, você precisa saber quando não usar certos recursos em c ++.
No geral, gosto de c ++. Eu uso isso na camada de serviços O / S, driver, código de gerenciamento, etc. Mas se sua equipe não tiver experiência suficiente com isso, será um desafio difícil.
Eu tive experiência com ambos. Quando o resto da equipe não estava pronto para isso, foi um desastre total. Por outro lado, foi uma boa experiência.
fonte
sim! Eu experimentei essas duas linguagens e o que descobri é que C ++ é uma linguagem mais amigável. Facilita com mais recursos. É melhor dizer que C ++ é um superconjunto da linguagem C, pois fornece recursos adicionais como polimorfismo, interança, sobrecarga de operador e função, tipos de dados definidos pelo usuário que não são realmente suportados em C. As mil linhas de código são reduzidas a poucas linhas com a ajuda da programação orientada a objetos é o principal motivo da mudança de C para C ++.
fonte