Pasar de C ++ a C

83

Después de unos años codificando en C ++, recientemente me ofrecieron un trabajo codificando en C, en el campo integrado.

Dejando de lado la cuestión de si es correcto o incorrecto descartar C ++ en el campo incrustado, hay algunas características / modismos en C ++ que extrañaría mucho. Sólo para nombrar unos pocos:

  • Estructuras de datos genéricas con seguridad de tipos (utilizando plantillas).
  • RAII. Especialmente en funciones con múltiples puntos de retorno, por ejemplo, no tener que acordarse de liberar el mutex en cada punto de retorno.
  • Destructores en general. Es decir, escribe un d'tor una vez para MyClass, luego, si una instancia de MyClass es miembro de MyOtherClass, MyOtherClass no tiene que desinicializar explícitamente la instancia de MyClass; su d'tor se llama automáticamente.
  • Espacios de nombres.

¿Cuáles son sus experiencias al pasar de C ++ a C?
¿Qué sustitutos de C encontró para sus características / modismos favoritos de C ++? ¿Descubrió alguna característica de C que le gustaría que tuviera C ++?

George
fuente
12
Probablemente debería ser un wiki de la comunidad si solo estás pidiendo experiencias y no consejos.
Peter Alexander
6
Quizás se encuentre interesado en Prog.SE .
11
@Peter: El OP ya no puede hacer preguntas en CW, y requería más reputación de la que tenía cuando aún era posible. Si cree que una pregunta debería convertirse en wiki de la comunidad por cualquier otro motivo que no sea para permitir que más usuarios editen las publicaciones "propiedad de la comunidad", lo que realmente desea es cerrar la pregunta.
4
¿No sería más adecuada esta pregunta en programmers.se? Ya que definitivamente es una pregunta "real", digo que la reabrimos y votamos para moverla. Y eso no es posible. OKAY.
Lasse V. Karlsen
21
El movimiento no sucederá hasta que el prog SE esté fuera de la versión beta y, en cualquier caso, creo que este enfoque de control de calidad es un desastre. Está fragmentando la comunidad, molestando a los usuarios, duplicando las preguntas y las respuestas. Está creando un caos de información desorganizada que antes era accesible y navegable en un solo sitio de "programador". Además, son preguntas como esta, que tienen una gran cantidad de vistas y una votación a favor increíble, las que me enojan entre los 5 golpes más cercanos y la comunidad en general.
Stefano Borini

Respuestas:

68

Trabajando en un proyecto incrustado, intenté trabajar en todo C una vez, y no pude soportarlo. Era tan detallado que hacía difícil leer cualquier cosa. Además, me gustaron los contenedores optimizados para incrustados que había escrito, que tenían que convertirse en #definebloques mucho menos seguros y más difíciles de arreglar .

Código que en C ++ se ve así:

if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
    pktQueue.Dequeue(1);

se convierte en:

if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
    Queue_Packet_Dequeue(pktQueue, 1);

lo que mucha gente probablemente dirá que está bien, pero se vuelve ridículo si tiene que hacer más de un par de llamadas al "método" en una línea. Dos líneas de C ++ se convertirían en cinco de C (debido a los límites de longitud de línea de 80 caracteres). Ambos generarían el mismo código, por lo que no es como si el procesador de destino se preocupara.

Una vez (allá por 1995), intenté escribir mucho C para un programa de procesamiento de datos multiprocesador. Del tipo en el que cada procesador tiene su propia memoria y programa. El compilador proporcionado por el proveedor era un compilador de C (una especie de derivado de HighC), sus bibliotecas eran de código cerrado, por lo que no podía usar GCC para compilar, y sus API se diseñaron con la mentalidad de que sus programas serían principalmente el proceso de inicialización / terminar la variedad, por lo que la comunicación entre procesadores era rudimentaria en el mejor de los casos.

Llegué aproximadamente un mes antes de que me rindiera , encontré una copia de cfront y lo pirateé en los archivos MAKE para poder usar C ++. Cfront ni siquiera admitía plantillas, pero el código C ++ era mucho, mucho más claro.

Estructuras de datos genéricas con seguridad de tipos (utilizando plantillas).

Lo más parecido que tiene C a las plantillas es declarar un archivo de encabezado con mucho código que se parece a:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }

luego tire de él con algo como:

#define TYPE Packet
#include "Queue.h"
#undef TYPE

Tenga en cuenta que esto no funcionará para tipos compuestos (por ejemplo, sin colas de unsigned char) a menos que realice una typedefprimera.

Ah, y recuerde, si este código no se usa en ningún lugar, entonces ni siquiera sabe si es sintácticamente correcto.

EDITAR: Una cosa más: deberá administrar manualmente la creación de instancias de código. Si su código de "plantilla" no son todas las funciones en línea, entonces tendrá que poner algo de control para asegurarse de que las cosas se instancian solo una vez para que su vinculador no escupe una pila de errores de "múltiples instancias de Foo" .

Para hacer esto, tendrá que poner las cosas que no están en línea en una sección de "implementación" en su archivo de encabezado:

#ifdef implementation_##TYPE

/* Non-inlines, "static members", global definitions, etc. go here. */

#endif

Y luego, en un lugar de todo su código por variante de plantilla , debe:

#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE

Además, esta sección aplicación necesita ser fuera de la norma #ifndef/ #define/ #endifletanía, ya que puede incluir el archivo de plantilla de encabezado en otro archivo de cabecera, pero necesario crear una instancia después en un .carchivo.

Sí, se pone feo rápidamente. Es por eso que la mayoría de los programadores de C ni siquiera lo intentan.

RAII.

Especialmente en funciones con múltiples puntos de retorno, por ejemplo, no tener que acordarse de liberar el mutex en cada punto de retorno.

Bueno, olvídate de tu bonito código y acostúmbrate a que todos tus puntos de retorno (excepto el final de la función) sean 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;
}

Destructores en general.

Es decir, escribe un d'tor una vez para MyClass, luego, si una instancia de MyClass es miembro de MyOtherClass, MyOtherClass no tiene que desinicializar explícitamente la instancia de MyClass; su d'tor se llama automáticamente.

La construcción de objetos debe manejarse explícitamente de la misma manera.

Espacios de nombres.

Eso es en realidad uno simple de arreglar: simplemente agregue un prefijo en cada símbolo. Esta es la causa principal de la hinchazón de la fuente de la que hablé anteriormente (ya que las clases son espacios de nombres implícitos). La gente de C ha estado viviendo esto, bueno, desde siempre, y probablemente no verán cuál es el problema.

YMMV

Mike DeSimone
fuente
59
Por supuesto que odias C si intentas forzarlo a ser C ++. Dudo que C ++ se vea bien si intentara imponerle características de $ more_expressive_language. No es una crítica a tu publicación, solo una observación :-)
Con respecto a la técnica goto-en-lugar-de-RAII: ¿No es una pesadilla de mantenimiento? es decir, cada vez que agrega una ruta de código que necesita limpieza, o incluso simplemente cambia el orden de las cosas dentro de la función, debe recordar ir a las etiquetas goto al final y cambiarlas también. Me gustaría ver una técnica que de alguna manera registre el código de limpieza justo al lado de lo que se necesita limpiar.
George
2
@george: Odio decirlo, pero la mayoría del código C incrustado que he visto es bastante malo para los estándares C. Por ejemplo, estoy trabajando con at91lib de Atmel en este momento, y requiere que escribas un archivo "board.h" que la mayoría de su código extrae como una dependencia. (Para su tablero de demostración, este encabezado tiene 792 líneas de largo). Además, una función "LowLevelInit ()" que debe personalizar para su tablero es casi en su totalidad registros de accesos, con líneas comoAT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
Mike DeSimone
1
Ah, y no hay nada que dice que BOARD_OSCOUNT(que es el valor del tiempo de espera para la espera de un reloj al conmutador;? Claro, eh) es en realidad una #defineen board.h. También hay, en esa misma función, una gran cantidad de código de bucle de giro copiado y pegado, que debería haberse convertido en dos líneas #define(y, cuando hice eso, guardó unos pocos bytes de código e hizo el función más legible al hacer que los conjuntos de registros y los bucles de giro sean más distintos). Una de las grandes razones para usar C es que te permite microgestionar y optimizar todo, pero la mayoría de los códigos que he visto no molestan.
Mike DeSimone
5
De acuerdo con @Mads. No hay razón para pasar por todo eso por funciones que realmente no necesita. Me parece que disfruto de un estilo similar a la biblioteca GTK. Defina sus "clases" como estructuras y luego cree métodos consistentes como my_class_new () y luego páselo a los "métodos": my_class_do_this (my_class_instance)
Máx.
17

Pasé de C ++ a C por una razón diferente (algún tipo de reacción alérgica;) y solo hay algunas cosas que extraño y algunas cosas que gané. Si se apega a C99, si puede, hay construcciones que le permiten programar de manera bastante agradable y segura, en particular

  • Los inicializadores designados (eventualmente combinados con macros) hacen que la inicialización de clases simples sea tan sencilla como los constructores
  • literales compuestos para variables temporales
  • for-la variable de alcance puede ayudarlo a realizar la administración de recursos vinculados al alcance , en particular para asegurarse unlockde mutex o freede matrices, incluso bajo retornos de función preliminares
  • __VA_ARGS__ Las macros se pueden usar para tener argumentos predeterminados para funciones y para desenrollar código
  • inline funciones y macros que se combinan bien para reemplazar (una especie de) funciones sobrecargadas
Jens Gustedt
fuente
2
@Mike: ¿para qué parte en particular? Si sigue el enlace que le di para los foralcances, aterrizará en P99, donde también puede buscar ejemplos y descripciones de las otras partes.
Jens Gustedt
1
@ Mike: Ahí tienes.
george
@george: ¡Gracias! @Jens: Ejemplos de los otros cuatro. Me he retrasado en mi C; la última vez que escuché que agregaron matrices asignadas automáticamente (es decir, pila) del tamaño de tiempo de ejecución (p void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }. ej. ) e inicialización de la estructura por nombres de campo (p struct Foo bar = { .field1 = 5, .field2 = 10 };. ej. ), este último que me encantaría ver en C ++, especialmente con objetos que no son POD (p UART uart[2] = { UART(0x378), UART(0x278) };. ej. ) .
Mike DeSimone
@ Mike: sí, hay matrices de longitud variable (VLA), pero pueden ser un poco peligrosas de usar debido a un posible desbordamiento de pila. El segundo que describe es exactamente el "inicializador designado", así que ahí va con su propio ejemplo ;-) Para los demás, encontrará información en el enlace P99 de arriba si hace clic en "Páginas relacionadas".
Jens Gustedt
8

No existe nada como el STL para C.
Hay bibliotecas disponibles que brindan una funcionalidad similar, pero ya no está integrada.

Creo que ese sería uno de mis mayores problemas ... Saber con qué herramienta podría solucionar el problema, pero no tener las herramientas disponibles en el idioma que tengo que utilizar.

MOnsDaR
fuente
esto es cierto. ¿Alguien podría explicar qué bibliotecas de clases de contenedores se deben usar para C? ¿O la respuesta es "escribe una tú mismo"?
Sandeep
@Sandeep: Para empezar, esta respuesta solo es correcta sobre los contenedores que no están en la biblioteca estándar. Además de carecer de un equivalente STL (la mejor parte de C ++), la biblioteca estándar de C es muy superior. POSIX contiene tsearch, lsearch, hsearch y bsearch además de qsort que está en libc. Glib es el "Boost" definitivo de C, fíjate que está repleto de golosinas (contenedores incluidos). library.gnome.org/devel/glib/stable . Glib también se integra con Gtk +, una combinación que supera a Boost y Qt. También está libapr, popular para cosas de xplatform como Subversion y Apache.
Matt Joiner
No puedo encontrar ninguna libs de c que pueda competir con stl, son más difíciles de usar, más difíciles de mantener, el rendimiento no es rival de stl cuando quieren mantener las c libs tan genéricas como stl. la limitación de c, y las razones por las que no podemos tener algo como stl en la biblioteca c, porque c simplemente no tiene la capacidad de desarrollar algo como stl.
StereoMatching
8

La diferencia entre C y C ++ es la previsibilidad del comportamiento del código.

Es más fácil predecir con gran precisión lo que hará su código en C, en C ++ puede volverse un poco más difícil llegar a una predicción exacta.

La predictibilidad en C le brinda un mejor control de lo que está haciendo su código, pero eso también significa que debe hacer más cosas.

En C ++ puedes escribir menos código para hacer lo mismo, pero (al menos para mí) ocasionalmente tengo problemas para saber cómo se presenta el código objeto en la memoria y su comportamiento esperado.

ds
fuente
4
Siempre que me preocupo por lo que realmente está haciendo el código, agrego la -sbandera gccpara obtener el volcado de ensamblaje, busco la función de interés y empiezo a leer. Es una excelente manera de aprender las peculiaridades de cualquier lenguaje compilado.
Mike DeSimone
2
También es una pérdida de tiempo, ya que el ensamblado generado en C ++ sería como leer Perl. Bravo por mirar de todos modos.
Matt Joiner
7

En mi línea de trabajo, que está incrustada, por cierto, estoy cambiando constantemente entre C y C ++.

Cuando estoy en C, extraño de C ++:

  • plantillas (incluidos, entre otros, contenedores STL). Los uso para cosas como contadores especiales, grupos de búfer, etc. (creé mi propia biblioteca de plantillas de clases y plantillas de funciones que utilizo en diferentes proyectos integrados)

  • biblioteca estándar muy potente

  • destructores, que por supuesto hacen posible RAII (mutex, desactivación de interrupciones, rastreo, etc.)

  • especificadores de acceso, para hacer cumplir mejor quién puede usar (no ver) qué

Utilizo la herencia en proyectos más grandes, y el soporte integrado de C ++ es mucho más limpio y agradable que el "truco" de C de incrustar la clase base como primer miembro (sin mencionar la invocación automática de constructores, listas de inicio, etc. ) pero los elementos enumerados anteriormente son los que más extraño.

Además, probablemente solo alrededor de un tercio de los proyectos C ++ incrustados en los que trabajo usan excepciones, por lo que me he acostumbrado a vivir sin ellas, por lo que no las extraño demasiado cuando vuelvo a C.

Por otro lado, cuando vuelvo a un proyecto de C con un número significativo de desarrolladores, hay clases completas de problemas de C ++ que estoy acostumbrado a explicar a la gente que desaparecen. La mayoría de los problemas se deben a la complejidad de C ++ y a las personas que creen que saben lo que está pasando, pero que en realidad están en la parte "C con clases" de la curva de confianza de C ++ .

Si tuviera la opción, preferiría usar C ++ en un proyecto, pero solo si el equipo es bastante sólido en el lenguaje. También, por supuesto, asumiendo que no es un proyecto de 8K μC en el que efectivamente estoy escribiendo "C" de todos modos.

Dan
fuente
2
Esa "curva de confianza de C ++" me molesta un poco. La forma en que está escrito y los comentarios implican que C ++ es inútil, una causa perdida o lo que sea. ¿Me estoy perdiendo de algo?
Mike DeSimone
Da el paso, nos vemos en unos años. La mayoría de los buenos programadores salen del otro lado con un sabor amargo.
Matt Joiner
3

Un par de observaciones

  • A menos que planee usar su compilador de C ++ para construir su C (lo cual es posible si se apega a un subconjunto bien definido de C ++), pronto descubrirá cosas que su compilador permite en C que serían un error de compilación en C ++.
  • No más errores de plantilla crípticos (¡yay!)
  • Sin programación orientada a objetos (compatible con lenguaje)
hhafez
fuente
C no admite plantilla no significa que no necesitemos "paradigmas genéricos", en C debe usar void * y macro para imitar la plantilla si necesita "paradigmas genéricos" .void * no es seguro para tipos, los errores de macro también bastante cutre, no mejor que template.Template es mucho más fácil de leer y mantener que macro, además de tipo seguro.
StereoMatching
2

Prácticamente las mismas razones que tengo para usar C ++ o una combinación de C / C ++ en lugar de C. puro. Puedo vivir sin espacios de nombres, pero los uso todo el tiempo si el código estándar lo permite. La razón es que puede escribir código mucho más compacto en C ++. Esto es muy útil para mí, escribo servidores en C ++ que tienden a fallar de vez en cuando. En ese punto, ayuda mucho si el código que está viendo es corto y consistente. Por ejemplo, considere el siguiente 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; 
}

En C que se ve así:

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; 
}

Ni un mundo de diferencia. Una línea más de código, pero eso tiende a sumarse. Normalmente, hace todo lo posible para mantenerlo limpio y esbelto, pero a veces tiene que hacer algo más complejo. Y en esas situaciones valora el número de líneas. Una línea más es una cosa más a tener en cuenta cuando intenta averiguar por qué su red de transmisión deja de enviar mensajes de repente.

De todos modos, encuentro que C ++ me permite hacer cosas más complejas de forma segura.

Anti héroe
fuente
En C no puede hacer esto "for (int i = 0".
Victor
6
Victor es válido c99 sin embargo (o compilar c con un compilador de c ++ para algunos problemas de escritura).
Roman A. Taycher
Encuentro que no puedo confiar en la seguridad. Entonces su mutex tiene alcance. Ahora no sabe por qué se desbloquea en caso de que "pase" una excepción. Ni siquiera sabe cuándo se desbloqueará, cualquier parte de su código podría decidir que ya tuvo suficiente y lanzar. Esas "medidas de seguridad" implícitas adicionales pueden estar enmascarando errores.
Matt Joiner
Matt, sabemos por qué se desbloqueó. En un caso normal, cuando el programa llega al final del alcance, el mutex se desbloqueará, no es necesario que lo desbloqueemos con códigos hechos a mano, es una pesadilla de mantenimiento. Si ocurre una excepción, el mutex se desbloqueará y podríamos detectar la excepción y leer el mensaje de error. El mensaje de error es suficientemente bueno o no, dependerá de cómo juegues con la excepción.
StereoMatching
0

Creo que el principal problema por el que c ++ es más difícil de aceptar en un entorno embebido se debe a la falta de ingenieros que entiendan cómo usar c ++ correctamente.

Sí, el mismo razonamiento también se puede aplicar a C, pero afortunadamente no hay tantas trampas en C que puedan dispararte en el pie. C ++, por otro lado, necesita saber cuándo no usar ciertas características en c ++.

Con todo, me gusta C ++. Lo uso en la capa de servicios de O / S, el controlador, el código de administración, etc. Pero si su equipo no tiene suficiente experiencia con eso, será un desafío difícil.

Tenía experiencia con ambos. Cuando el resto del equipo no estaba preparado para ello, fue un desastre total. Por otro lado, fue una buena experiencia.

KOkon
fuente
0

¡si! He experimentado ambos lenguajes y lo que encontré es que C ++ es un lenguaje más amigable. Facilita con más funciones. Es mejor decir que C ++ es un superconjunto del lenguaje C, ya que proporciona características adicionales como polimorfismo, interitancia, sobrecarga de operadores y funciones, tipos de datos definidos por el usuario que realmente no son compatibles con C.Las mil líneas de código se reducen a pocas líneas con la ayuda de la programación orientada a objetos es la principal razón para pasar de C a C ++.

Kaynat Liaqat
fuente
C ++ realmente no es un superconjunto de C; Es muy fácil escribir código C que no se compilará en un compilador C ++. Por otro lado, Objective-C era (¿es?) Un superconjunto estricto de C.
ex nihilo
@exnihilo El superconjunto de convenciones aquí es para definir que C ++ viene con más funciones. También mejora la sintaxis y la semántica y reduce las posibilidades de error. Hay algunos códigos que no se compilaron en C ++ pero sí en C, como const int a; Esto dará un error en C ++ ya que es necesario inicializar una constante en el momento de la declaración mientras que en C se compilará el concepto. Entonces, el superconjunto no es una regla rígida y rápida como en matemáticas (A⊂B), sino que es una aproximación de que C ++ proporciona características adicionales como el concepto orientado a objetos.
kaynat liaqat