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 ++?
Respuestas:
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
#define
bloques 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 unatypedef
primera.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
/#endif
letaní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.c
archivo.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
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; }
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
fuente
AT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
BOARD_OSCOUNT
(que es el valor del tiempo de espera para la espera de un reloj al conmutador;? Claro, eh) es en realidad una#define
enboard.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.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
for
-la variable de alcance puede ayudarlo a realizar la administración de recursos vinculados al alcance , en particular para asegurarseunlock
de mutex ofree
de 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ódigoinline
funciones y macros que se combinan bien para reemplazar (una especie de) funciones sobrecargadasfuente
for
alcances, aterrizará en P99, donde también puede buscar ejemplos y descripciones de las otras partes.void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }
. ej. ) e inicialización de la estructura por nombres de campo (pstruct Foo bar = { .field1 = 5, .field2 = 10 };
. ej. ), este último que me encantaría ver en C ++, especialmente con objetos que no son POD (pUART uart[2] = { UART(0x378), UART(0x278) };
. ej. ) .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.
fuente
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.
fuente
-s
banderagcc
para 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.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.
fuente
Un par de observaciones
fuente
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.
fuente
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.
fuente
¡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 ++.
fuente