Mudança de C ++ para C

83

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?

george
fonte
12
Provavelmente deve ser wiki da comunidade se você está apenas pedindo experiências e não conselhos.
Peter Alexander
6
Você pode estar interessado em Prog.SE .
11
@Peter: Perguntas não podem mais ser feitas CW pelo OP, e isso exigia mais repetição do que ele tinha quando ainda era possível. Se você acha que uma pergunta deve ser criada como wiki da comunidade por qualquer outro motivo, além de permitir que mais usuários editem as postagens "pertencentes à comunidade", então o que você realmente deseja é encerrar a questão.
4
Esta pergunta não seria mais adequada em programmers.se? Como é definitivamente uma pergunta "real", eu digo para reabri-la e votar para movê-la. E isso não é possível. ESTÁ BEM.
Lasse V. Karlsen
21
A mudança não acontecerá até que o SE prog saia da versão beta e, de qualquer forma, acho que essa abordagem de controle de qualidade é um estorvo. Está fragmentando a comunidade, irritando os usuários, duplicando as perguntas e as respostas. Está criando um caos de informações desorganizadas que antes eram acessíveis e navegáveis ​​em um único site de "programador". Além disso, são questões como esta, tendo enormes visualizações e incríveis votos positivos, que me deixam furioso entre o 5 whack-a-close e a comunidade como um todo.
Stefano Borini

Respostas:

68

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 um typedefprimeiro.

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/ #endiflitany, porque você pode incluir o arquivo de cabeçalho de modelo em outro arquivo de cabeçalho, mas precisa instanciar depois em um .carquivo.

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 gotos:

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

Mike DeSimone
fonte
59
É claro que você odeia C se tentar forçá-lo a ser C ++. Eu duvido que C ++ ficaria ótimo se você tentasse impor recursos de $ more_expressive_language nele também. Não é uma crítica ao seu post, apenas uma observação :-)
Em relação à técnica de ir para em vez de RAII: não é um pesadelo de manutenção? ou seja, sempre que você adiciona um caminho de código que precisa de limpeza, ou mesmo apenas altera a ordem das coisas dentro da função, você deve se lembrar de ir aos rótulos goto no final e alterá-los também. Eu gostaria de ver uma técnica que de alguma forma registre o código de limpeza ao lado do que precisa ser limpo.
george
2
@george: Odeio dizer isso, mas a maior parte do código C incorporado que vi é muito ruim para os padrões C. Por exemplo, estou trabalhando com o at91lib da Atmel agora, e ele exige que você escreva um arquivo "board.h" que a maior parte do código extrai como uma dependência. (Para a placa de demonstração, este cabeçalho tem 792 linhas.) Além disso, uma função "LowLevelInit ()" que você precisa personalizar para sua placa é quase inteiramente registrar acessos, com linhas comoAT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
Mike DeSimone
1
Ah, e nada lá diz-lhe que BOARD_OSCOUNT(que é o valor do limite de tempo para espera de um relógio para interruptor,? Claro, huh) é realmente um #defineno board.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.
Mike DeSimone,
5
Concorde com @Mads. Não há razão para passar por tudo isso para recursos de que você realmente não precisa. Acho que gosto de um estilo semelhante ao da biblioteca GTK. Defina suas "classes" como estruturas e, em seguida, crie métodos consistentes, como my_class_new () e, em seguida, passe isso para os "métodos": my_class_do_this (my_class_instance)
Máx.
17

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

  • inicializadores designados (eventualmente combinados com macros) tornam a inicialização de classes simples tão indolor quanto os construtores
  • literais compostos para variáveis ​​temporárias
  • forA variável de escopo pode ajudá-lo a fazer o gerenciamento de recursos vinculados ao escopo , em particular para garantir a unlockmutexes ou freearrays, 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ódigo
  • inline funções e macros que combinam bem para substituir (meio que) funções sobrecarregadas
Jens Gustedt
fonte
2
@Mike: para qual parte em particular? Se você seguir o link que forneci para os forescopos, você chegará ao P99, onde também poderá procurar exemplos e descrições das outras partes.
Jens Gustedt,
1
@Mike: Aí está.
george
@george: Obrigado! @Jens: Exemplos dos outros quatro. Fiquei para trás no meu C; pela última vez que ouvi, eles adicionaram arrays de alocação automática de tamanho de tempo de execução (isto é, pilha) (por exemplo void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }) e inicialização de estrutura por nomes de campo (por exemplo struct Foo bar = { .field1 = 5, .field2 = 10 };), este último que eu adoraria ver em C ++, especialmente com objetos não POD (por exemplo UART uart[2] = { UART(0x378), UART(0x278) };) .
Mike DeSimone,
@ Mike: sim, existem matrizes de comprimento variável (VLA), mas podem ser um pouco perigosos de usar devido ao potencial stackoverflow. O segundo que você descreve é ​​exatamente o "inicializador designado", então aí está seu próprio exemplo ;-) Para os outros, você encontrará informações no link P99 acima se clicar em "Páginas relacionadas".
Jens Gustedt,
8

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.

MOnsDaR
fonte
isso é verdade. Alguém poderia explicar quais bibliotecas de classe de contêiner devem ser usadas para C? Ou a resposta é "escreva um para você"?
Sandeep
@Sandeep: Para começar, esta resposta é correta apenas sobre os contêineres não estarem na biblioteca padrão. Além de não ter um equivalente STL (a melhor parte do C ++), a biblioteca padrão C é muito superior. POSIX contém tsearch, lsearch, hsearch e bsearch, além de qsort que está em libc. Glib é o "Boost" definitivo do C, veja que vem cheio de guloseimas (contêineres incluídos). library.gnome.org/devel/glib/stable . Glib também se integra com Gtk +, uma combinação que excede Boost e Qt. Também existe o libapr, popular para materiais de plataforma como Subversion e Apache.
Matt Joiner de
Não consigo encontrar nenhuma libs de c que possa competir com a stl, são mais difíceis de usar, mais difíceis de manter, o desempenho não é o rival de stl quando eles querem manter as libs c tão genéricas quanto stl. a limitação de c e os motivos pelos quais não podemos ter algo como stl na biblioteca c, porque c simplesmente não tem a capacidade de desenvolver algo como stl.
StereoMatching de
8

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.

ds
fonte
4
Sempre que me preocupo com o que o código está realmente fazendo, adiciono o -ssinalizador gccpara 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.
Mike DeSimone,
2
Também é uma perda de tempo, visto que o assembly gerado pelo C ++ seria como ler Perl. Bravo por procurar de qualquer maneira.
Matt Joiner de
7

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.

Dan
fonte
2
Essa "curva de confiança C ++" me incomoda um pouco. A maneira como está escrito e os comentários implicam que C ++ é um caso perdido, uma causa perdida ou o que for. Estou esquecendo de algo?
Mike DeSimone,
Mergulhe, veremos você em alguns anos. A maioria dos bons programadores sai do outro lado com um gosto amargo.
Matt Joiner de
3

Algumas observações

  • A menos que você planeje usar seu compilador C ++ para construir seu C (o que é possível se você se ater a um subconjunto bem definido de C ++), você logo descobrirá coisas que seu compilador permite em C que seriam um erro de compilação em C ++.
  • Chega de erros de template enigmáticos (yay!)
  • Nenhuma programação orientada a objetos (com suporte para linguagem)
Hhafez
fonte
C não suporta template não significa que não precisamos de "paradigmas genéricos", em C você tem que usar void * e macro para imitar o template se você precisar de "paradigmas genéricos" .void * não é seguro para tipos, os erros de macro também muito ruim, não melhor do que template.Template é muito mais fácil de ler e manter do que macro, além de digitar seguro.
StereoMatching de
2

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.

Anti-herói
fonte
Em C, você não pode fazer isso "for (int i = 0".
Victor
6
Victor, porém, é válido c99 (ou compilar c com um compilador c ++ para algum problema de digitação).
Roman A. Taycher
Acho que não posso confiar na segurança. Portanto, seu mutex tem escopo. Agora você não sabe por que ele está sendo desbloqueado se uma exceção "vagar". Você nem sabe quando será desbloqueado, qualquer parte do seu código pode decidir que está farto e jogar. Essas "proteções" implícitas extras podem mascarar bugs.
Matt Joiner de
Matt, nós sabemos por que ele foi desbloqueado. No caso normal, quando o programa atinge o fim do escopo, o mutex será desbloqueado, não precisamos desbloqueá-lo com códigos feitos à mão, é um pesadelo de manutenção. Se houver ocorrer uma exceção, o mutex será desbloqueado e poderemos capturar a exceção e ler a mensagem de erro dela. A mensagem de erro é boa o suficiente ou não depende de como você joga com a exceção.
StereoMatching
0

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.

KOkon
fonte
0

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 ++.

kaynat liaqat
fonte
C ++ realmente não é um superconjunto de C; é muito fácil escrever código C que não conseguirá compilar em um compilador C ++. Por outro lado, Objective-C era (é?) Um superconjunto estrito de C.
ex nihilo
@exnihilo O superconjunto de convenção aqui é para definir que C ++ vem com mais recursos. Também melhora a sintaxe e a semântica e reduz as chances de erro. Existem alguns códigos que não compilam em C ++, mas podem em C, como const int a; Isso gerará um erro em C ++, pois é necessário inicializar uma constante no momento da declaração, enquanto em C o conceito será compilado. Portanto, o superconjunto não é uma regra rígida e rápida como na matemática (A⊂B), mas sim uma aproximação de que o C ++ fornece recursos adicionais, como o conceito de orientação a objetos.
kaynat liaqat