Pregunta

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

Clasifique la respuesta por motivos tales como:

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

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

¿Fue útil?

Solución

RTTI y manejo de excepciones:

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

Plantillas:

  • tenga cuidado con ellos si el tamaño del código es un problema.Si su CPU de destino no tiene caché de instrucciones o solo tiene una muy pequeña, también puede reducir el rendimiento.(Las plantillas tienden a inflar el código si se usan sin cuidado).Otra metaprogramación inteligente también puede disminuir el tamaño del código.No hay una respuesta clara en la suya.

Funciones virtuales y herencia:

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

Otros consejos

La elección de evitar ciertas características siempre debe estar impulsada por un análisis cuantitativo del comportamiento de su software, en su hardware, con su cadena de herramientas elegida, bajo las restricciones su el dominio implica.Hay muchas cosas que no se deben hacer en el desarrollo de C++ y que se basan en supersticiones e historia antigua en lugar de datos concretos.Desafortunadamente, esto a menudo da como resultado que se escriba una gran cantidad de código de solución adicional para evitar el uso de funciones con las que alguien, en algún lugar, tuvo problemas alguna vez.

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

Mirar aquí para ver un ejemplo bastante bueno de un estándar de codificación escrito para c++ integrado.

El documento "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 ++" también brinda buena información sobre la programación en C++ para un dispositivo integrado.

Es una lectura interesante para el Razón fundamental temprano Estándar C++ integrado

Mira esto artículo en EC ++ también.

El estándar de C++ integrado era un subconjunto adecuado de C++, es decir.no tiene añadidos.Se eliminaron las siguientes características de idioma:

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

Está anotado en el pagina wiki Que Bjarne Stroustrup dice (de la EC ++ Std), "según mi conocimiento, EC ++ está muerto (2004), y si no es así, debería ser". Stroustrup continúa recomendando el documento al que hace referencia la respuesta de Prakash.

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

Si está utilizando un ARM7TDMI, evitar accesos a memoria no alineados cueste lo que cueste.

El núcleo ARM7TDMI básico no tiene verificación de alineación y devolverá datos rotados cuando realice una lectura no alineada.Algunas implementaciones tienen circuitos adicionales para elevar un ABORT excepción, pero si no tiene una de esas implementaciones, encontrar errores debido a accesos no alineados 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, esto 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 (lo que depende de dónde se encuentra "x" y qué opciones del compilador 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 deseada.

En la mayoría de los sistemas no desea utilizar nuevo / borrar a menos que los haya anulado con su propia implementación que se extrae de su propio montón administrado.Sí, funcionará, pero se trata de un sistema con memoria limitada.

No habría dicho que hay una regla estricta para esto;Depende mucho de tu aplicación.Los sistemas integrados suelen ser:

  • Más limitados en la cantidad de memoria que tienen disponible.
  • A menudo se ejecuta en hardware más lento
  • Tiende a estar más cerca del hardware, es decir.conducirlo de alguna manera como jugar con la configuración del registro.

Sin embargo, al igual que cualquier otro desarrollo, debes equilibrar todos los puntos que has mencionado con los requisitos que te dieron o derivaron.

En cuanto al exceso de código, creo que es mucho más probable que el culpable sea 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> (); }

Lo más probable es que el vinculador combine todas las definiciones de 'foo' en una única 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 vinculador no hace esto, puede usar una creación de instancias explícita para hacerlo 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 considere 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 insertar 'foo' por usted, terminará con 3 copias diferentes de 'foo'.¡No hay plantillas a la vista!

EDITAR: De un comentario anterior de InSciTek Jeff

Al utilizar instancias explícitas para las funciones que sabe que se usarán únicamente, 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'.

Las funciones de tiempo suelen depender del sistema operativo (a menos que las reescriba).Utilice sus propias funciones (especialmente si tiene un RTC)

Se pueden usar plantillas siempre que tenga suficiente espacio para el código; de lo contrario, no las use.

las excepciones tampoco son muy portátiles

funciones printf que no escribir en un búfer no es portátil (debe estar conectado de alguna manera al sistema de archivos para escribir en un ARCHIVO* con printf).Utilice sólo las funciones sprintf, snprintf y str* (strcat, strlen) y, por supuesto, sus corresponsales de caracteres amplios (wcslen...).

Si la velocidad es el problema, tal vez debería usar sus propios contenedores en lugar de STL (por ejemplo, el contenedor std::map para asegurarse de que una clave sea igual). 2 (si 2) comparaciones con el operador 'menos' ( a [menos que] b == false && b [menos que] a == false significa a == b ).'menos' es el único parámetro de comparación recibido por la clase std::map (y no solo).Esto puede provocar 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 probablemente deban reescribirse también porque dependen del sistema operativo de muchas maneras (especialmente cuando se trata de asignación de memoria de seguridad de subprocesos).

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

a veces deberías usar Pulgar en lugar del modo Armado.Puede mejorar el rendimiento.

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

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

  • El brazo SDT produce un código más estricto y más rápido pero es muy caro (> eur5k por asiento!).En mi trabajo anterior usamos este compilador y estaba bien.

  • Sin embargo, las herramientas del brazo de GCC 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 bits de ARM (como el GBA) también hay una ventaja de velocidad.

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

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

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

Para los sistemas integrados, principalmente querrá evitar cosas que tengan un costo de tiempo de ejecución anormal definido.Algunos ejemplos:excepciones, y RTTI (para incluir transmisión_dinámica y mecanografiado).

Asegúrese de saber qué funciones admite el compilador para su plataforma integrada y también asegúrese de conocer las peculiaridades de su plataforma.Por ejemplo, el compilador CodeComposer de TI no crea instancias automáticas de plantillas.Como resultado, si desea utilizar la clasificación de STL, debe crear instancias de cinco cosas diferentes manualmente.Tampoco admite transmisiones.

Otro ejemplo es que puede estar utilizando un chip DSP, que no tiene soporte de hardware para operaciones de punto flotante.Eso significa que cada vez que usas un float o un double pagas el costo de una llamada a función.

En resumen, conozca todo lo que hay que saber sobre su plataforma integrada y su compilador, y luego sabrá qué características evitar.

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 momento, el vinculador solicitó la eliminación del operador (void *).No tengo idea de por qué sucede eso y agregar una definición vacía para ese operador solucionó el problema.

Tenga en cuenta que el costo de las excepciones depende de su código.En una aplicación que perfilé (una relativamente pequeña en ARM968), el soporte de excepciones 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 sólo en caso de que sucediera algo grave, es decir,nunca en la práctica, lo que mantuvo el tiempo de ejecución muy bajo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top