Aspectos básicos de la codificación
Importante
Esta es la documentación de Azure Sphere (heredado). Azure Sphere (heredado) se retira el 27 de septiembre de 2027 y los usuarios deben migrar a Azure Sphere (integrado) en este momento. Use el selector de versiones situado encima de la TOC para ver la documentación de Azure Sphere (integrado).
Se recomienda que el código de la aplicación cumpla un estándar de calidad mínimo, tal como se define en este tema. A través de nuestras asociaciones con los clientes que buscan mejorar sus aplicaciones implementadas en producción hemos encontrado algunos problemas comunes, que cuando se ha corregido mejorar el rendimiento de las aplicaciones.
Problemas comunes
- Al establecer el conjunto de API de destino, se recomienda usar las herramientas de CMake y Azure Sphere más recientes y, en última instancia, compilar los archivos binarios de versión final estableciendo
AZURE_SPHERE_TARGET_API_SET="latest-lts"
. Para obtener más información, consulte Codificación para la seguridad renovable.
Nota:
Al crear específicamente paquetes de imagen para transferir localmente dentro de un proceso de fabricación, establezca AZURE_SPHERE_TARGET_API_SET
en la versión adecuada del sistema operativo de Azure Sphere con la que el dispositivo se ha originado o recuperado; si no lo hace, el sistema operativo de Azure Sphere rechazará el paquete de imágenes.
- Cuando esté listo para implementar una aplicación en producción, asegúrese de compilar los paquetes de imagen finales en modo de versión.
- Es habitual ver las aplicaciones implementadas en producción a pesar de las advertencias del compilador. La aplicación de una directiva de advertencias cero para compilaciones completas garantiza que cada advertencia del compilador se solucione intencionadamente. A continuación se muestran los tipos de advertencia más frecuentes, que se recomienda encarecidamente abordar:
- Advertencias implícitas relacionadas con la conversión: los errores a menudo se presentan debido a conversiones implícitas resultantes de implementaciones iniciales rápidas que no se han revisado. Por ejemplo, el código que tiene muchas conversiones numéricas implícitas entre distintos tipos numéricos puede provocar una pérdida de precisión crítica o incluso errores de cálculo o bifurcación. Para ajustar correctamente todos los tipos numéricos, se recomiendan tanto el análisis intencionado como la conversión, no solo la conversión.
- Evite modificar los tipos de parámetro esperados: al llamar a las API, si no se convierte explícitamente, las conversiones implícitas pueden provocar problemas; por ejemplo, anular un búfer cuando se usa un tipo numérico firmado en lugar de un tipo numérico sin firmar.
- advertencias de descarte const: cuando una función requiere un tipo const como parámetro, reemplazarlo puede provocar errores y un comportamiento impredecible. El motivo de la advertencia es asegurarse de que el parámetro const permanece intacto y tiene en cuenta las restricciones al diseñar una determinada API o función.
- Advertencias de parámetros o punteros incompatibles: omitir esta advertencia a menudo puede ocultar errores que serán difíciles de realizar el seguimiento más adelante. Eliminar estas advertencias puede ayudar a reducir el tiempo de diagnóstico de otros problemas de la aplicación.
- La configuración de una canalización coherente de CI/CD es clave para la administración sostenible de aplicaciones a largo plazo, ya que permite volver a compilar archivos binarios y sus símbolos correspondientes para depurar versiones anteriores de aplicaciones. Una estrategia de bifurcación adecuada también es esencial para el seguimiento de versiones y evita el costoso espacio en disco en el almacenamiento de datos binarios.
Problemas relacionados con la memoria
- Cuando sea posible, defina todas las cadenas fijas comunes como
global const char*
en lugar de codificarlas de forma rígida (por ejemplo, dentroprintf
de los comandos) para que se puedan usar como punteros de datos en todo el código base mientras se mantiene el código más fácil de mantener. En las aplicaciones del mundo real, la recolección de texto común de registros o manipulaciones de cadenas (comoOK
,Succeeded
o nombres de propiedades JSON) y la globalización de las constantes a menudo ha dado lugar a ahorros en la sección de memoria de datos de solo lectura (también conocida como .rodata), lo que se traduce en ahorros en memoria flash que se pueden usar en otras secciones (como .text para más código). Este escenario suele pasarse por alto, pero puede producir ahorros significativos en memoria flash.
Nota:
También se puede lograr lo anterior simplemente activando optimizaciones del compilador (por ejemplo -fmerge-constants
, en gcc). Si elige este enfoque, inspeccione también la salida del compilador y compruebe que se han aplicado las optimizaciones deseadas, ya que pueden variar en distintas versiones del compilador.
- En el caso de las estructuras de datos globales, siempre que sea posible, considere la posibilidad de proporcionar longitudes fijas a miembros de matriz razonablemente pequeños en lugar de usar punteros para la memoria asignada dinámicamente. Por ejemplo:
typedef struct {
int chID;
...
char chName[SIZEOF_CHANNEL_NAME]; // This approach is preferable, and easier to use e.g. in a function stack.
char *chName; // Unless this points to a constant, tracking a memory buffer introduces more complexity, to be weighed with the cost/benefit, especially when using multiple instances of the structure.
...
} myConfig;
- Evite la asignación de memoria dinámica siempre que sea posible, especialmente dentro de las funciones llamadas con frecuencia.
- En C, busque funciones que devuelvan un puntero a un búfer de memoria y considere la posibilidad de convertirlas en funciones que devuelven un puntero de búfer al que se hace referencia y su tamaño relacionado con los autores de llamada. La razón para ello es que devolver solo un puntero a un búfer a menudo ha provocado problemas con el código de llamada, ya que el tamaño del búfer devuelto no se reconoce forzosamente y, por lo tanto, podría poner en riesgo la coherencia del montón. Por ejemplo:
// This approach is preferable:
MY_RESULT_TYPE getBuffer(void **ptr, size_t &size, [...other parameters..])
// This should be avoided, as it lacks tracking the size of the returned buffer and a dedicated result code:
void *getBuffer([...other parameters..])
Contenedores dinámicos y búferes
Los contenedores como listas y vectores también se usan con frecuencia en aplicaciones de C insertadas, con la advertencia de que debido a las limitaciones de memoria en el uso de bibliotecas estándar, normalmente deben codificarse explícitamente o vincularse como bibliotecas. Estas implementaciones de biblioteca pueden desencadenar un uso intensivo de memoria si no se diseñan cuidadosamente.
Además de las matrices típicas asignadas estáticamente o implementaciones muy dinámicas de memoria, se recomienda un enfoque de asignación incremental. Por ejemplo, comience con una implementación de cola vacía de N objetos asignados previamente; en la inserción de cola (N+1), la cola crece por un X objetos asignados previamente (N=N+X) fijos, que permanecerán asignados dinámicamente hasta que otra adición a la cola desbordará su capacidad actual e incrementará la asignación de memoria por X objetos previamente asignados previamente. Finalmente, puede implementar una nueva función de compactación para llamar a moderación (ya que sería demasiado caro llamar de forma periódica) para reclamar memoria no utilizada.
Un índice dedicado conservará dinámicamente el número de objetos activos de la cola, que se puede limitar a un valor máximo para la protección de desbordamiento adicional.
Este enfoque elimina el "chatter" generado por la asignación continua de memoria y la desasignación en las implementaciones de cola tradicionales. Para más información, consulte Administración y uso de memoria. Puede implementar enfoques similares para estructuras como listas, matrices, etc.