Pregunta ¿Hay alguna razón para usar C en lugar de C ++ para el desarrollo integrado?


Pregunta

Tengo dos compiladores en mi hardware C ++ y C89

Estoy pensando en usar C ++ con clases pero sin polimorfismo (para evitar los vtables). Las principales razones por las que me gustaría usar C ++ son:

  • Prefiero usar funciones "en línea" en lugar de definiciones de macro.
  • Me gustaría usar espacios de nombres, ya que prefijo desordenar el código.
  • Veo que C ++ es un poco más seguro, principalmente debido a las plantillas y al detallado moldeado.
  • Me gustan mucho las funciones y los constructores sobrecargados (utilizados para la fundición automática).

¿Ve alguna razón para seguir con C89 cuando se desarrolla para hardware muy limitado (4kb de RAM)?

Conclusión

¡Gracias por tus respuestas, fueron realmente útiles!

Pensé en el tema y me quedaré con C principalmente porque:

  1. Es más fácil predecir el código real en C y esto es realmente importante si solo tienes 4kb de ram.
  2. Mi equipo consiste principalmente en desarrolladores de C, por lo que las funciones avanzadas de C ++ no se usarán con frecuencia.
  3. Encontré una forma de incorporar funciones en mi compilador de C (C89).

Es difícil aceptar una respuesta ya que proporcionó tantas buenas respuestas. Desafortunadamente no puedo crear una wiki y aceptarla, así que elegiré una respuesta que me hizo pensar más.


74
2018-05-01 18:51


origen


Respuestas:


Dos razones para usar C sobre C ++:

  1. Para muchos procesadores integrados, no existe un compilador de C ++, o tienes que pagar extra por ello.
  2. Mi experiencia es que una proporción significativa de ingenieros de software integrados tienen poca o ninguna experiencia de C ++, ya sea por (1) o porque tiende a no enseñarse en grados de ingeniería electrónica, por lo que sería mejor seguir con lo que ellos saben

Además, la pregunta original y una serie de comentarios mencionan los 4 Kb de RAM. Para un procesador integrado típico, la cantidad de RAM (en su mayoría) no está relacionada con el tamaño del código, ya que el código está almacenado y se ejecuta desde, flash.

Ciertamente, la cantidad de espacio de almacenamiento de código es algo a tener en cuenta, pero a medida que aparecen procesadores nuevos y más espaciosos en el mercado, es un problema menor de lo que solía ser para todos, excepto para los proyectos más sensibles a los costos.

Sobre el uso de un subconjunto de C ++ para su uso con sistemas integrados: ahora hay un MISRA C ++ estándar, que puede valer la pena mirar.

EDITAR: Ver también esta pregunta, lo que llevó a un debate sobre C vs C ++ para sistemas integrados.


40
2018-05-02 16:58



Para muy un objetivo con recursos limitados, como 4 KB de RAM, probaría las aguas con algunas muestras antes de comprometer un gran esfuerzo que no se pueda transferir fácilmente a una implementación pura de ANSI C.

El grupo de trabajo Embedded C ++ sí propuso un subconjunto estándar del lenguaje y un subconjunto estándar de la biblioteca estándar para ir con él. Perdí la pista de ese esfuerzo cuando el C User's Journal murió, desafortunadamente. Parece que hay un artículo en Wikipediay que el comité todavía existe.

En un entorno incrustado, realmente debe tener cuidado con la asignación de memoria. Para hacer cumplir esa atención, es posible que deba definir el cuidado global operator new() y sus amigos a algo que ni siquiera se puede vincular para que sepa que no se usa. Colocación new por otro lado, es probable que sea su amigo, cuando se usa juiciosamente junto con un esquema de asignación estable, seguro para subprocesos y latencia garantizada.

Las funciones en línea no causarán muchos problemas, a menos que sean lo suficientemente grandes como para haber sido verdaderas funciones en primer lugar. Por supuesto, las macros que reemplazaron tenían el mismo problema.

Las plantillas, también, pueden no causar un problema a menos que su instanciación se ejecute fuera de control. Para cualquier plantilla que utilice, audite el código generado (el mapa del enlace puede tener suficientes pistas) para asegurarse de que solo ocurrieron las instancias que pretendía usar.

Otro problema que puede surgir es la compatibilidad con su depurador. No es inusual que un depurador de hardware que de otro modo se pueda utilizar tenga un soporte muy limitado para interactuar con el código fuente original. Si efectivamente debe depurar en ensamblado, entonces el interesante cambio de nombre de C ++ puede agregar confusión adicional a la tarea.

RTTI, moldes dinámicos, herencia múltiple, polimorfismo pesado y excepciones vienen con una cierta cantidad de costo de tiempo de ejecución para su uso. Algunas de esas funciones tienen un nivel que cuesta sobre todo el programa si se usan, otras solo aumentan el peso de las clases que las necesitan. Conozca la diferencia y elija las funciones avanzadas sabiamente con pleno conocimiento de al menos un análisis de costo / beneficio superficial.

En un entorno incrustado pequeño, se vinculará directamente a un kernel en tiempo real o se ejecutará directamente en el hardware. De cualquier manera, deberá asegurarse de que su código de inicio de ejecución maneje las tareas de inicio específicas de C ++ correctamente. Esto puede ser tan simple como asegurarse de usar las opciones de enlazador correctas, pero como es común tener control directo sobre la fuente al punto de entrada de restablecimiento de encendido, es posible que deba auditar eso para asegurarse de que hace todo. Por ejemplo, en una plataforma ColdFire en la que trabajé, las herramientas dev se enviaron con un módulo CRT0.S que tenía los inicializadores C ++ presentes pero comentados. Si lo hubiera utilizado directamente desde la caja, me habría desconcertado los objetos globales cuyos constructores nunca se habían ejecutado en absoluto.

Además, en un entorno incrustado, a menudo es necesario inicializar los dispositivos de hardware antes de que puedan ser utilizados, y si no hay un sistema operativo ni un gestor de arranque, entonces es su código el que lo hace. Deberá recordar que se ejecutan constructores para objetos globales antes de  main() se llama por lo que deberá modificar su CRT0.S local (o su equivalente) para realizar la inicialización del hardware antes de los constructores globales se llaman. Obviamente, la parte superior de main() es demasiado tarde


59
2018-05-01 20:55



No. Cualquiera de las características del lenguaje C ++ que podrían causar problemas (polimorfismo en tiempo de ejecución, RTTI, etc.) se puede evitar al realizar el desarrollo integrado. Existe una comunidad de desarrolladores de C ++ incrustados (recuerdo haber leído columnas de desarrolladores incrustados usando C ++ en el antiguo C / C ++ Users 'Journal), y no puedo imaginar que sean muy elocuentes si la elección es tan mala.


25
2018-05-01 18:55



los Informe técnico sobre el rendimiento de C ++ es una gran guía para este tipo de cosas. ¡Tenga en cuenta que tiene una sección sobre problemas de programación incorporada!

Además, ++ sobre la mención de Embedded C ++ en las respuestas. El estándar no es 100% para mi gusto, pero es un buen punto de referencia a la hora de decidir qué partes de C ++ podrías descartar.

Al programar para plataformas pequeñas, desactivamos excepciones y RTTI, evitamos la herencia virtual y prestamos mucha atención a la cantidad de funciones virtuales que tenemos por ahí.

Sin embargo, su amigo es el mapa del enlazador: lo verifica con frecuencia, y detectará rápidamente las fuentes de código y la acumulación de memoria estática.

Después de eso, se aplican las consideraciones de uso de la memoria dinámica estándar: en un entorno tan restringido como el que usted menciona, es posible que no desee usar asignaciones dinámicas. Algunas veces puede salirse con la suya con grupos de memoria para pequeños allocos dinámicos, o asignación "basada en cuadros" donde preasigna un bloque y lo descarta todo más tarde.


20
2018-05-01 21:22



Recomiendo usar el compilador de C ++, pero limitar el uso de las características específicas de C ++. Puede programar como C en C ++ (el tiempo de ejecución de C se incluye al hacer C ++, aunque en la mayoría de las aplicaciones integradas no hace uso de la biblioteca estándar de todos modos).

Puede continuar y usar clases de C ++, etc., solo

  • Limite su uso de funciones virtuales (como ha dicho)
  • Limite su uso de plantillas
  • Para una plataforma incrustada, querrá anular el operador nuevo y / o utilizar la ubicación nueva para la asignación de memoria.

16
2018-05-01 19:00



Como ingeniero de firmware / sistema integrado, puedo decirles algunas de las razones por las que C sigue siendo la opción n. ° 1 en comparación con C ++ y, sí, domino ambos.

1) Algunos objetivos que desarrollamos tienen 64kB de RAM para código y datos, por lo que debe asegurarse de que cada byte cuente, y sí, he tratado la optimización de código para ahorrar 4 bytes que me costó 2 horas, y eso es todo 2008.

2) Todas las funciones de la biblioteca C se revisan antes de que las dejemos en el código final, debido a la limitación de tamaño, por lo que preferimos que no se use divisor (sin divisor de hardware, por lo que se necesita una gran biblioteca), malloc (porque no tenemos ningún montón , toda la memoria se asigna desde el búfer de datos en un fragmento de 512 bytes y debe ser revisada por el código), u otra práctica orientada a objetos que conlleve penalizaciones grandes. Recuerde, cada función de la biblioteca que usa cuenta.

3) ¿Has oído hablar del término superposición? tienes tan poco espacio de código que a veces tienes que intercambiar cosas con otro conjunto de código. Si llama a una función de biblioteca, entonces la función de biblioteca debe ser residente. Si solo lo usa en una función de superposición, está desperdiciando mucho espacio confiando en demasiados métodos orientados a objetos. Por lo tanto, no asuma ninguna función de biblioteca C, y mucho menos C ++ para ser aceptada.

4) Se necesita moldear e incluso empaquetar (donde la estructura de datos no alineada cruza el límite de palabras) debido al diseño de hardware limitado (es decir, un motor ECC que está cableado de cierta manera) o para hacer frente a un error de hardware. No se puede asumir demasiado en forma implícita, entonces, ¿por qué orientarlo demasiado?

5) El peor de los casos: eliminar algunos de los métodos orientados a objetos obligará a los desarrolladores a pensar antes de usar recursos que puedan explotar (es decir, asignar 512 bytes en una pila en lugar de un búfer de datos) y evitar algunos de los posibles escenarios de peor caso no se prueban o eliminan la totalidad de la ruta del código.

6) Usamos mucha abstracción para mantener el hardware alejado del software y hacer que el código sea lo más portátil posible, y amigable con la simulación. El acceso al hardware debe estar envuelto en una macro o función en línea que se compila condicionalmente entre plataformas diferentes, el tipo de datos debe formarse como tamaño de byte en lugar de como objetivo específico, no se permite el uso directo del puntero (porque alguna plataforma asume que la E / S asignada a la memoria es lo mismo que la memoria de datos), etc.

Puedo pensar en más, pero entiendes la idea. Los tipos de firmware de nosotros tienen formación orientada a objetos, pero la tarea del sistema integrado puede ser tan orientada al hardware y de bajo nivel, que no es de alto nivel ni abstraible por naturaleza.

Por cierto, cada trabajo de firmware en el que he estado usa el control de fuente, no sé de dónde sacas esa idea.

-Algunos tipos de firmware de SanDisk.


13
2017-10-18 02:56



Mi preferencia personal es C porque:

  • Sé lo que hace cada línea de código (y cuesta)
  • No conozco C ++ lo suficiente como para saber qué hace cada línea de código (y cuesta)

¿Por qué la gente dice esto? Tú no lo hagas saber qué está haciendo cada línea de C a menos que compruebe la salida de asm. Lo mismo vale para C ++.

Por ejemplo, ¿qué ASM produce esta afirmación inocente?

a[i] = b[j] * c[k];

Parece bastante inocente, pero un compilador basado en gcc produce esta asm para un micro de 8 bits

CLRF 0x1f, ACCESS
RLCF 0xfdb, W, ACCESS
ANDLW 0xfe
RLCF 0x1f, F, ACCESS
MOVWF 0x1e, ACCESS
MOVLW 0xf9
MOVF 0xfdb, W, ACCESS
ADDWF 0x1e, W, ACCESS
MOVWF 0xfe9, ACCESS
MOVLW 0xfa
MOVF 0xfdb, W, ACCESS
ADDWFC 0x1f, W, ACCESS
MOVWF 0xfea, ACCESS
MOVFF 0xfee, 0x1c
NOP
MOVFF 0xfef, 0x1d
NOP
MOVLW 0x1
CLRF 0x1b, ACCESS
RLCF 0xfdb, W, ACCESS
ANDLW 0xfe
RLCF 0x1b, F, ACCESS
MOVWF 0x1a, ACCESS
MOVLW 0xfb
MOVF 0xfdb, W, ACCESS
ADDWF 0x1a, W, ACCESS
MOVWF 0xfe9, ACCESS
MOVLW 0xfc
MOVF 0xfdb, W, ACCESS
ADDWFC 0x1b, W, ACCESS
MOVWF 0xfea, ACCESS
MOVFF 0xfee, 0x18
NOP
MOVFF 0xfef, 0x19
NOP
MOVFF 0x18, 0x8
NOP
MOVFF 0x19, 0x9
NOP
MOVFF 0x1c, 0xd
NOP
MOVFF 0x1d, 0xe
NOP
CALL 0x2142, 0
NOP
MOVFF 0x6, 0x16
NOP
MOVFF 0x7, 0x17
NOP
CLRF 0x15, ACCESS
RLCF 0xfdf, W, ACCESS
ANDLW 0xfe
RLCF 0x15, F, ACCESS
MOVWF 0x14, ACCESS
MOVLW 0xfd
MOVF 0xfdb, W, ACCESS
ADDWF 0x14, W, ACCESS
MOVWF 0xfe9, ACCESS
MOVLW 0xfe
MOVF 0xfdb, W, ACCESS
ADDWFC 0x15, W, ACCESS
MOVWF 0xfea, ACCESS
MOVFF 0x16, 0xfee
NOP
MOVFF 0x17, 0xfed
NOP

El número de instrucciones producidas depende masivamente de:

  • Los tamaños de a, b y c.
  • si esos punteros se almacenan en la pila o son globales
  • si i, j y k están en la pila o son globales

Esto es especialmente cierto en el pequeño mundo integrado, donde los procesadores simplemente no están configurados para manejar C. Así que mi respuesta sería que C y C ++ son tan malos como el uno al otro, a menos que siempre examinen la salida de asm, en cuyo caso son tan buenos como el uno al otro.

Hugo


9
2018-03-15 12:07



He oído que algunas personas prefieren C para el trabajo integrado debido a que es más simple y, por lo tanto, es más fácil predecir el código real que se generará.

Personalmente, creo que escribir C-style C ++ (usando plantillas para seguridad tipográfica) te daría muchas ventajas y no veo ninguna razón real para no hacerlo.


8
2018-05-01 18:55