c++ stl embedded

C++ incrustado: ¿para usar STL o no?



embedded (11)

Súper seguro y pierde mucho de lo que constituye C ++ (imo, es más que solo la definición del lenguaje) y tal vez se encuentre con problemas más adelante o tenga que agregar un montón de manejo de excepciones y tal vez algún otro código ahora.

Tenemos un debate similar en el mundo del juego y las personas bajan de ambos lados. En cuanto a la parte citada, ¿por qué le preocuparía perder "mucho de lo que constituye C ++"? Si no es pragmático, no lo use. No debería importar si es "C ++" o no.

Ejecute algunas pruebas. ¿Puedes manejar la administración de memoria de STL de forma que te satisfaga? Si es así, ¿valió la pena el esfuerzo? Una gran cantidad de problemas STL y boost están diseñados para resolver simplemente no aparecen si se diseña para evitar la asignación aleatoria de memoria dinámica ... ¿STL resuelve un problema específico que enfrenta?

Mucha gente ha abordado STL en entornos difíciles y ha sido feliz con él. Mucha gente simplemente lo evita. Algunas personas proponen estándares completamente nuevos . No creo que haya una respuesta correcta.

Siempre he sido un ingeniero de software incrustado, pero generalmente en la Capa 3 o 2 de la pila OSI. No soy realmente un tipo de hardware. En general siempre he hecho productos de telecomunicaciones, generalmente teléfonos de mano / teléfonos celulares, lo que generalmente significa algo así como un procesador ARM 7.

Ahora me encuentro en un mundo embebido más genérico, en una pequeña empresa emergente, donde podría pasar a procesadores "no tan potentes" (está el bit subjetivo). No puedo predecir cuál.

He leído bastante sobre el debate sobre el uso de STL en C ++ en sistemas integrados y no hay una respuesta clara. Hay algunas pequeñas preocupaciones sobre la portabilidad y algunas sobre el tamaño del código o el tiempo de ejecución, pero tengo dos preocupaciones principales:
1 - manejo de excepciones; Todavía no estoy seguro de si usarlo (vea Embedded C ++: ¿para usar excepciones o no? )
2 - No me agrada la asignación de memoria dinámica en sistemas integrados, debido a los problemas que puede presentar. En general, tengo un grupo de búferes que está asignado estáticamente en el momento de la compilación y que solo sirve para búferes de tamaño fijo (si no hay búferes, reinicio del sistema). El STL, por supuesto, hace una gran cantidad de asignación dinámica.

Ahora tengo que tomar la decisión de usar o renunciar al STL, para toda la compañía, para siempre (se trata de algo muy básico).

¿De qué manera salta? Súper seguro y pierde mucho de lo que constituye C ++ (imo, es más que solo la definición del lenguaje) y tal vez se encuentre con problemas más adelante o tenga que agregar un montón de manejo de excepciones y tal vez algún otro código ahora.

Estoy tentado de ir con Boost , pero 1) No estoy seguro de si se transferirá a cada procesador integrado que pueda querer usar y 2) en su sitio web, dicen que no garantizan / recomiendan ciertas partes del mismo para sistemas integrados (especialmente FSM, que parece extraño). Si busco Boost, encontramos un problema más adelante ...


Permítanme comenzar diciendo que no he trabajado de forma integrada durante algunos años, y nunca en C ++, así que mi consejo vale cada centavo que pagas por ello ...

Las plantillas utilizadas por STL nunca generarán código que no necesitaría generar usted mismo, por lo que no me preocuparía por el código inflado.

El STL no arroja excepciones por sí mismo, por lo que no debería ser una preocupación. Si tus clases no arrojan, deberías estar a salvo. Divida la inicialización de su objeto en dos partes, permita que el constructor cree un objeto básico y luego realice cualquier inicialización que pueda fallar en una función miembro que devuelva un código de error.

Creo que todas las clases de contenedor le permitirán definir su propia función de asignación, por lo que si desea asignar desde un grupo puede hacerlo realidad.


  1. para la gestión de memoria, puede implementar su propio asignador, que solicita memoria desde el grupo. Y todo el contenedor STL tiene una plantilla para el asignador.

  2. por excepción, STL no arroja muchas excepciones, en general, las más comunes son: memoria insuficiente, en su caso, el sistema debe reiniciarse, por lo que puede restablecer en el asignador. otros están fuera de alcance, puede evitarlo por parte del usuario.

  3. entonces, creo que puedes usar STL en el sistema integrado :)


Además de todos los comentarios, le propongo que lea el Informe técnico sobre el rendimiento de C ++ que aborda específicamente los temas que le interesan: usar C ++ en sistemas integrados (incluidos los sistemas duros en tiempo real); cómo se implementa generalmente el manejo de excepciones y qué sobrecarga tiene; gastos generales de asignación de tienda gratis.

El informe es muy bueno ya que desenmascara muchas colas populares sobre el rendimiento de C ++.


Básicamente depende de tu compilador y de la cantidad de memoria que tienes. Si tiene más de unos pocos Kb de RAM, tener asignación de memoria dinámica ayuda mucho. Si la implementación de malloc de la biblioteca estándar que tiene no está ajustada al tamaño de su memoria, puede escribir la suya, o hay buenos ejemplos como mm_malloc de Ralph Hempel que puede usar para escribir sus operadores nuevos y eliminar en la parte superior .

No estoy de acuerdo con aquellos que repiten el meme de que las excepciones y los contenedores STL son demasiado lentos o demasiado hinchados, etc. Por supuesto, agrega un poco más de código que un malloc de C simple, pero el uso juicioso de las excepciones puede hacer que el código sea mucho más claro y evitar demasiado error al revisar la propaganda en C.

Hay que tener en cuenta que los asignadores de STL aumentarán sus asignaciones en potencias de dos, lo que significa que a veces hará reasignaciones hasta que alcance el tamaño correcto, lo que se puede evitar con reserva, por lo que se vuelve tan barato como un malloc del deseado tamaño si conoce el tamaño para asignar de todos modos.

Si tiene un gran búfer en un vector, por ejemplo, en algún momento puede hacer una reasignación y termina usando hasta 1.5 veces el tamaño de la memoria que tiene la intención de usar en algún momento mientras reasigna y mueve datos. (Por ejemplo, en algún punto tiene N bytes asignados, agrega datos a través de un anexo o un iterador de inserción y asigna 2N bytes, copia el primer N y libera N. Usted tiene 3N bytes asignados en algún punto).

Entonces, al final tiene muchas ventajas, y paga si sabes lo que estás haciendo. Debes saber un poco de cómo funciona C ++ para usarlo en proyectos integrados sin sorpresas.

Y para el tipo de los búferes fijos y el restablecimiento, siempre puede reiniciar dentro del nuevo operador o lo que sea si se ha quedado sin memoria, pero eso significaría que hizo un mal diseño que puede agotar su memoria.

Una excepción lanzada con ARM realview 3.1:

--- OSD/#1504 throw fapi_error("OSDHANDLER_BitBlitFill",res); S:218E72F0 E1A00000 MOV r0,r0 S:218E72F4 E58D0004 STR r0,[sp,#4] S:218E72F8 E1A02000 MOV r2,r0 S:218E72FC E24F109C ADR r1,{pc}-0x94 ; 0x218e7268 S:218E7300 E28D0010 ADD r0,sp,#0x10 S:218E7304 FA0621E3 BLX _ZNSsC1EPKcRKSaIcE <0x21a6fa98> S:218E7308 E1A0B000 MOV r11,r0 S:218E730C E1A0200A MOV r2,r10 S:218E7310 E1A01000 MOV r1,r0 S:218E7314 E28D0014 ADD r0,sp,#0x14 S:218E7318 EB05C35F BL fapi_error::fapi_error <0x21a5809c> S:218E731C E3A00008 MOV r0,#8 S:218E7320 FA056C58 BLX __cxa_allocate_exception <0x21a42488> S:218E7324 E58D0008 STR r0,[sp,#8] S:218E7328 E28D1014 ADD r1,sp,#0x14 S:218E732C EB05C340 BL _ZN10fapi_errorC1ERKS_ <0x21a58034> S:218E7330 E58D0008 STR r0,[sp,#8] S:218E7334 E28D0014 ADD r0,sp,#0x14 S:218E7338 EB05C36E BL _ZN10fapi_errorD1Ev <0x21a580f8> S:218E733C E51F2F98 LDR r2,0x218e63ac <OSD/#1126> S:218E7340 E51F1F98 LDR r1,0x218e63b0 <OSD/#1126> S:218E7344 E59D0008 LDR r0,[sp,#8] S:218E7348 FB056D05 BLX __cxa_throw <0x21a42766>

No parece ser tan aterrador, y no se agrega ninguna sobrecarga dentro de {} bloques o funciones si no se lanza la excepción.


El mayor problema con STL en sistemas integrados es el problema de asignación de memoria (que, como dijiste, causa muchos problemas).

Investigar seriamente la creación de tu propia gestión de memoria, construida anulando los operadores nuevos / eliminar. Estoy bastante seguro de que con un poco de tiempo, se puede hacer, y es casi seguro que vale la pena.

En cuanto al problema de las excepciones, no iría allí. Las excepciones son una desaceleración grave de su código, ya que provocan que cada bloque ( { } ) tenga código antes y después, lo que permite capturar la excepción y la destrucción de cualquier objeto contenido dentro. No tengo datos concretos al respecto, pero cada vez que veo surgir este problema, he visto una abrumadora evidencia de una desaceleración masiva causada por el uso de excepciones.

Editar:
Dado que mucha gente escribió comentarios que indicaban que el manejo de excepciones no es más lento, pensé en agregar esta pequeña nota (gracias por la gente que escribió esto en los comentarios, pensé que sería bueno agregarlo aquí).

La razón por la cual el manejo de excepciones ralentiza su código se debe a que el compilador debe asegurarse de que cada bloque ( {} ), desde el lugar en que se lanza una excepción hasta el lugar donde se trata, debe desasignar cualquier objeto dentro de él. Este es un código que se agrega a cada bloque, independientemente de si alguna vez alguien lanza una excepción o no (ya que el compilador no puede decir en tiempo de compilación si este bloque formará parte de una "cadena" de excepción).

Por supuesto, esta podría ser una forma antigua de hacer las cosas que se ha vuelto mucho más rápida en los compiladores más nuevos (no estoy exactamente actualizado en las optimizaciones del compilador C ++). La mejor forma de saber es simplemente ejecutar un código de muestra, con excepciones activadas y desactivadas (y que incluye algunas funciones anidadas), y cronometrar la diferencia.


Trabajo en sistemas integrados en tiempo real todos los días. Por supuesto, mi definición de sistema integrado puede ser diferente a la tuya. Pero hacemos un uso completo del STL y las excepciones y no experimentamos ningún problema inmanejable. También hacemos uso de la memoria dinámica (a una tasa muy alta, asignando muchos paquetes por segundo, etc.) y aún no hemos necesitado recurrir a asignadores personalizados o grupos de memoria. Incluso hemos usado C ++ en controladores de interrupción. No usamos el impulso, pero solo porque una cierta agencia del gobierno no nos lo permite.

Según nuestra experiencia, puede utilizar muchas características modernas de C ++ en un entorno integrado, siempre que use su cabeza y realice sus propios puntos de referencia. Recomiendo encarecidamente que utilice la 3ª edición Effective C ++ de Scott Meyer, así como los estándares de codificación C ++ de Sutter y Alexandrescu para ayudarlo a utilizar C ++ con un estilo de programación sensato.

Editar: Después de obtener un voto positivo en este 2 años más tarde, déjame publicar una actualización. Estamos mucho más avanzados en nuestro desarrollo y finalmente hemos llegado a puntos en nuestro código donde los contenedores de bibliotecas estándar son demasiado lentos en condiciones de alto rendimiento. Aquí, de hecho, recurrimos a algoritmos personalizados, grupos de memoria y contenedores simplificados. Esa es la belleza de C ++, sin embargo, puede usar la biblioteca estándar y obtener todas las cosas buenas que proporciona para el 90% de sus casos de uso. No lo descarta cuando encuentra problemas, simplemente optimiza manualmente los puntos problemáticos.


Electronic Arts escribió un largo tratado sobre por qué el STL no era apropiado para el desarrollo de consolas integradas y por qué tenían que escribir el suyo. Es un artículo detallado, pero las razones más importantes fueron:

  1. Los asignadores de STL son lentos, hinchados e ineficientes
  2. Los compiladores en realidad no son muy buenos para incluir todas esas llamadas a funciones profundas
  3. Los asignadores de STL no admiten la alineación explícita
  4. Los algoritmos STL que vienen con GCC y STL de MSVC no son muy eficientes, porque son muy independientes de la plataforma y por lo tanto pierden muchas microoptimizaciones que pueden marcar una gran diferencia.

Hace algunos años, nuestra empresa tomó la decisión de no utilizar el STL en absoluto, sino que implementó nuestro propio sistema de contenedores que son de máximo rendimiento, más fáciles de depurar y más conservadores de la memoria. Fue mucho trabajo, pero se ha amortizado muchas veces. Pero el nuestro es un espacio en el que los productos compiten en cuánto pueden meter en 16.6ms con una CPU y un tamaño de memoria dados.

En cuanto a las excepciones: son lentas en las consolas, y cualquiera que le diga lo contrario no ha intentado sincronizarlas. Simplemente compilar con ellos habilitados ralentizará todo el programa debido al código de prólogo / epílogo necesario; mídalo usted mismo si no me cree. Es aún peor en las CPU en orden que en el x86. Por esta razón, el compilador que usamos ni siquiera es compatible con las excepciones de C ++.

La ganancia de rendimiento no se debe tanto a evitar el costo de un lanzamiento de excepción, sino también a deshabilitar por completo las excepciones.


En nuestro proyecto de escáner integrado estábamos desarrollando una placa con CPU ARM7 y STL no traía ningún problema. Seguramente los detalles del proyecto son importantes ya que la asignación de memoria dinámica puede no ser un problema para muchos tableros disponibles hoy en día y tipo de proyectos.


El proyecto de código abierto "Embedded Template Library (ETL)" se enfoca en los problemas habituales con el STL utilizado en las aplicaciones incorporadas al proporcionar / implementar una biblioteca:

  • comportamiento determinista
  • "Cree un conjunto de contenedores donde se determine el tamaño o el tamaño máximo en el momento de la compilación. Estos contenedores deberían ser en gran medida equivalentes a los suministrados en el STL, con una API compatible".
  • sin asignación de memoria dinámica
  • no se requiere RTTI
  • poco uso de funciones virtuales (solo cuando sea absolutamente necesario)
  • conjunto de contenedores de capacidad fija
  • almacenamiento en caché de contenedores de forma amistosa como bloque de memoria asignada continuamente
  • tamaño reducido del código del contenedor
  • Enumeraciones inteligentes de tipo seguro
  • Cálculos de CRC
  • sumas de comprobación y funciones hash
  • variantes = tipo de tipo de unión segura
  • Elección de afirmaciones, excepciones, controlador de errores o no verificaciones de errores
  • fuertemente unidad probada
  • código fuente bien documentado
  • y otras características ...

También puede considerar un C ++ STL comercial para Desarrolladores integrados proporcionado por ESR Labs.


Las otras publicaciones han abordado los problemas importantes de la asignación de memoria dinámica, las excepciones y la posible saturación de código. Solo quiero agregar: ¡no te olvides de <algorithm> ! Independientemente de si utiliza vectores STL o matrices y punteros simples en C, aún puede usar sort() , binary_search() , random_shuffle() , las funciones para random_shuffle() y administrar montones, etc. Estas rutinas casi con seguridad serán más rápidas y menos problemáticas. que las versiones que construyes tú mismo.

Ejemplo: a menos que lo piense cuidadosamente, es probable que un algoritmo de mezcla que usted construya produzca distribuciones asimétricas ; random_shuffle() no lo hará.