versiones programacion lenguaje historia evolucion definicion caracteristicas bjarne aplicaciones c++ embedded
Tecnología de la información - Lenguajes de programación, sus entornos e interfaces de software del sistema - Informe técnico sobre el rendimiento de C ++

programacion - Uso de C++ en sistemas integrados



lenguaje c versiones (17)

¿Qué características de C ++ deberían evitarse en los sistemas integrados?

Por favor, clasifique la respuesta por razones tales como:

  • uso de memoria
  • tamaño del código
  • velocidad
  • portabilidad

EDITAR: Permite usar un ARM7TDMI con 64k RAM como objetivo para controlar el alcance de las respuestas.


Asegúrese de saber qué características son compatibles con el compilador para su plataforma incrustada y también asegúrese de conocer las peculiaridades de su plataforma. Por ejemplo, el compilador CodeComposer de TI no realiza instancias de plantillas automáticas. Como resultado, si desea usar el tipo de STL, necesita crear una instancia de cinco cosas diferentes manualmente. Tampoco es compatible con las transmisiones.

Otro ejemplo es que puede estar usando un chip DSP, que no tiene soporte de hardware para operaciones de punto flotante. Esto significa que cada vez que usa un flotador o un doble usted paga el costo de una llamada a función.

Para resumir, sepa todo lo que hay que saber sobre su plataforma incorporada y su compilador, y luego sabrá qué características evitar.


En cuanto a la hinchazón del código, creo que es mucho más probable que el culpable esté en línea que las plantillas.

Por ejemplo:

// foo.h template <typename T> void foo () { /* some relatively large definition */ } // b1.cc #include "foo.h" void b1 () { foo<int> (); } // b2.cc #include "foo.h" void b2 () { foo<int> (); } // b3.cc #include "foo.h" void b3 () { foo<int> (); }

El vinculador probablemente fusionará todas las definiciones de ''foo'' en una sola unidad de traducción. Por lo tanto, el tamaño de ''foo'' no es diferente al de cualquier otra función de espacio de nombres.

Si su enlazador no hace esto, puede usar una instanciación explícita para hacer eso por usted:

// foo.h template <typename T> void foo (); // foo.cc #include "foo.h" template <typename T> void foo () { /* some relatively large definition */ } template void foo<int> (); // Definition of ''foo<int>'' only in this TU // b1.cc #include "foo.h" void b1 () { foo<int> (); } // b2.cc #include "foo.h" void b2 () { foo<int> (); } // b3.cc #include "foo.h" void b3 () { foo<int> (); }

Ahora considera lo siguiente:

// foo.h inline void foo () { /* some relatively large definition */ } // b1.cc #include "foo.h" void b1 () { foo (); } // b2.cc #include "foo.h" void b2 () { foo (); } // b3.cc #include "foo.h" void b3 () { foo (); }

Si el compilador decide alinear ''foo'' para ti, terminarás con 3 copias diferentes de ''foo''. ¡No hay plantillas a la vista!

EDITAR: De un comentario anterior de InSciTek Jeff

Al usar instancias explícitas para las funciones que sabe que se usarán solo, también puede asegurarse de que se eliminen todas las funciones no utilizadas (lo que en realidad puede reducir el tamaño del código en comparación con el caso sin plantilla):

// a.h template <typename T> class A { public: void f1(); // will be called void f2(); // will be called void f3(); // is never called } // a.cc #include "a.h" template <typename T> void A<T>::f1 () { /* ... */ } template <typename T> void A<T>::f2 () { /* ... */ } template <typename T> void A<T>::f3 () { /* ... */ } template void A<int>::f1 (); template void A<int>::f2 ();

A menos que su cadena de herramientas esté completamente rota, lo anterior generará código solo para ''f1'' y ''f2''.


Es probable que las excepciones sean la respuesta más común sobre qué evitar. La mayoría de las implementaciones tienen un costo bastante grande de memoria estática o un costo de memoria en tiempo de ejecución. También tienden a hacer que las garantías en tiempo real sean más difíciles.

Busque aquí un buen ejemplo de un estándar de codificación escrito para C ++ incrustado.


La elección de evitar ciertas características siempre debe ser impulsada por un análisis cuantitativo del comportamiento de su software, en su hardware, con su cadena de herramientas elegida, bajo las limitaciones que su dominio conlleva. Hay una gran cantidad de sabiduría convencional "no hacer" en el desarrollo de C ++ que se basan en la superstición y la historia antigua en lugar de datos duros. Desafortunadamente, esto a menudo resulta en una gran cantidad de código de solución extra que se escribe para evitar el uso de características que alguien, en algún lugar, tenía un problema con alguna vez.


No hubiera dicho que hay una regla difícil y rápida para esto; Depende mucho de tu aplicación. Los sistemas integrados son típicamente:

  • Más limitado en la cantidad de memoria que tienen disponible
  • A menudo se ejecuta en hardware más lento
  • Tienden a estar más cerca del hardware, es decir, manejarlo de alguna manera como juguetear con las configuraciones de registro.

Sin embargo, al igual que cualquier otro desarrollo, debe equilibrar todos los puntos que ha mencionado en relación con los requisitos que se le dieron / obtuvieron.


Para los sistemas integrados, preferirá evitar cosas que tienen un costo definido de tiempo anormal. Algunos ejemplos: excepciones y RTTI (para incluir dynamic_cast y typeid ).


Si está utilizando un entorno de desarrollo orientado hacia el desarrollo integrado o un sistema integrado particular, debería haberle limitado algunas de las opciones. Dependiendo de las capacidades de recursos de su objetivo, desactivará algunos de los elementos mencionados anteriormente (RTTI, excepciones, etc.). Esta es la ruta más fácil de seguir, en lugar de tener en cuenta lo que aumentará el tamaño o los requisitos de memoria (aunque, de todos modos, debes conocerlo mentalmente).


Un problema particular que me sorprendió con ATMega GCC 3. algo: cuando agregué una función de brasa virtual a una de mis clases, tuve que agregar un destructor virtual. En ese punto, el enlazador solicitó la eliminación del operador (void *). No tengo idea de por qué sucede eso, y al agregar una definición vacía para ese operador, se resuelve el problema.


Usando un ARM7 y asumiendo que no tiene una MMU externa, los problemas de asignación de memoria dinámica pueden ser más difíciles de depurar. Agregaría el "uso juicioso de new / delete / free / malloc" a la lista de directrices.


RTTI y manejo de excepciones:

  • Aumenta el tamaño del código
  • Disminuye el rendimiento
  • A menudo puede ser reemplazado por mecanismos más baratos o un mejor diseño de software.

Plantillas:

  • tenga cuidado con ellos si el tamaño del código es un problema. Si su CPU objetivo no tiene o solo tiene una memoria caché muy pequeña, también puede reducir el rendimiento. (las plantillas tienden a inflar el código si se usan sin cuidado). La meta-programación de Otoh también puede disminuir el tamaño del código. No hay una respuesta clara en el suyo.

Funciones virtuales y herencia

  • Estos están bien para mí. Escribo casi todo mi código incrustado en C. Eso no me impide usar tablas de puntero de función para imitar funciones virtuales. Nunca se convirtieron en un problema de rendimiento.

En la mayoría de los sistemas, no desea usar new / delete a menos que los haya anulado con su propia implementación que extraiga de su propio montón administrado. Sí, será un trabajo, pero se trata de un sistema con memoria limitada.


Es una lectura interesante para el Fundamento del primer standrard incrustado de C ++

Ver este artículo en EC ++ también.

El std incrustado de C ++ era un subconjunto propio de C ++, es decir, no tiene adiciones. Se eliminaron las siguientes funciones de idioma:

  • Herencia múltiple
  • Clases base virtuales
  • Información de tipo de tiempo de ejecución (typeid)
  • Nuevas versiones de estilo (static_cast, dynamic_cast, reinterpret_cast y const_cast)
  • El calificador de tipo mutable
  • Espacios de nombres
  • Excepciones
  • Plantillas

En la página de la wiki, Bjarne Stroustrup dice (de EC ++ std): "A mi leal saber y entender, EC ++ está muerto (2004), y si no es así, debería serlo". Stroustrup continúa recomendando el documento referenciado por la respuesta de Prakash.


Si está utilizando un ARM7TDMI, evite accesos de memoria no alineados a toda costa .

El núcleo ARM7TDMI básico no tiene comprobación de alineación, y devolverá los datos girados cuando se realiza una lectura desalineada. Algunas implementaciones tienen circuitos adicionales para generar una excepción ABORT , pero si no tiene una de esas implementaciones, encontrar errores debido a accesos desalineados es muy doloroso.

Ejemplo:

const char x[] = "ARM7TDMI"; unsigned int y = *reinterpret_cast<const unsigned int*>(&x[3]); printf("%c%c%c%c/n", y, y>>8, y>>16, y>>24);

  • En una CPU x86 / x64, esto imprime "7TDM".
  • En una CPU SPARC, este vuelca el núcleo con un error de bus.
  • En una CPU ARM7TDMI, esto podría imprimir algo como "7ARM" o "ITDM", suponiendo que la variable "x" está alineada en un límite de 32 bits (que depende de dónde se encuentra "x" y qué opciones de compilación están en uso , etc.) y estás usando el modo little-endian. Es un comportamiento indefinido, pero está prácticamente garantizado que no funcionará de la manera que desee.

Tenga en cuenta que el costo de las excepciones depende de su código. En una aplicación que hice un perfil (una relativamente pequeña en ARM968), el soporte de excepción agregó un 2% al tiempo de ejecución y el tamaño del código aumentó en 9.5 KB. En esta aplicación, se lanzaron excepciones solo en caso de que sucediera algo gravemente malo, es decir, nunca en la práctica, lo que mantuvo el tiempo de ejecución muy bajo.


las funciones de tiempo generalmente dependen del sistema operativo (a menos que las reescriba). Use sus propias funciones (especialmente si tiene un RTC)

las plantillas están bien para usar siempre que tenga suficiente espacio para el código - othwerise no las use

las excepciones no son muy portátiles también

Las funciones printf que no escriben en un búfer no son portátiles (debe estar conectado de alguna manera al sistema de archivos para escribir en un ARCHIVO * con printf). Utilice solo funciones sprintf, snprintf y str * (strcat, strlen) y, por supuesto, sus corespondents de caracteres anchos (wcslen ...).

Si el problema es la velocidad, quizás deba usar sus propios contenedores en lugar de STL (por ejemplo, el contenedor std :: map para asegurarse de que una clave es igual realiza 2 (sí 2) comparaciones con el operador ''less'' (a [menor que] b == falso && b [menor que] a == falso significa a == b). ''menos'' es el único parámetro de comparación recibido por la clase std :: map (y no solo). Esto puede llevar a una cierta pérdida de rendimiento en rutinas críticas

plantillas, las excepciones aumentan el tamaño del código (puede estar seguro de esto). a veces incluso el rendimiento se ve afectado al tener un código más grande.

las funciones de asignación de memoria también necesitan ser reescritas porque dependen del sistema operativo de muchas maneras (especialmente cuando se trata de la asignación de memoria de seguridad de subprocesos).

malloc usa la variable _end (generalmente declarada en el script del enlazador) para asignar memoria, pero esto no es seguro para subprocesos en entornos "desconocidos".

a veces deberías usar el pulgar en lugar del modo de armado. Puede mejorar el rendimiento.

Entonces, para 64k de memoria, diría que C ++ con algunas de sus características agradables (STL, excepciones, etc.) puede ser exagerado. Definitivamente elegiría C.


Habiendo utilizado tanto el compilador GCC ARM como el propio SDT del ARM, tendría los siguientes comentarios:

  • El ARM SDT produce un código más ajustado y rápido, pero es muy costoso (> Eur5k por asiento). En mi trabajo anterior utilizamos este compilador y estaba bien.

  • Sin embargo, las herramientas GCC ARM funcionan muy bien y es lo que uso en mis propios proyectos (GBA / DS).

  • Use el modo ''pulgar'' ya que esto reduce el tamaño del código significativamente. En las variantes de bus de 16 bit del ARM (como el GBA) también hay una ventaja de velocidad.

  • 64k es muy pequeño para el desarrollo de C ++. Usaría C & Assembler en ese entorno.

En una plataforma tan pequeña, tendrás que tener cuidado con el uso de la pila. Evite la recursividad, estructuras de datos automáticas (locales) de gran tamaño, etc. El uso del montón también será un problema (nuevo, malloc, etc.). C le dará más control de estos problemas.