He visto algunas de las bibliotecas para microcontroladores y sus funciones hacen una cosa a la vez. Por ejemplo, algo como esto:
void setCLK()
{
// Code to set the clock
}
void setConfig()
{
// Code to set the config
}
void setSomethingElse()
{
// 1 line code to write something to a register.
}
Luego, aparecen otras funciones que utilizan este código de 1 línea que contiene una función para otros fines. Por ejemplo:
void initModule()
{
setCLK();
setConfig();
setSomethingElse();
}
No estoy seguro, pero creo que de esta manera crearía más llamadas a saltos y crearía una sobrecarga de apilar las direcciones de retorno cada vez que se llama o sale de una función. Y eso haría que el programa funcione lento, ¿verdad?
He buscado y en todas partes dicen que la regla básica de la programación es que una función debe realizar solo una tarea.
Entonces, si escribo directamente un módulo de función InitModule que establece el reloj, agrega alguna configuración deseada y hace algo más sin llamar a las funciones. ¿Es un mal enfoque al escribir software incrustado?
EDITAR 2:
Parece que mucha gente ha entendido esta pregunta como si estuviera tratando de optimizar un programa. No, no tengo intenciones de hacer . Estoy dejando que el compilador lo haga, porque siempre será (¡aunque espero que no!) Mejor que yo.
Todos me culpan por elegir un ejemplo que represente algún código de inicialización . La pregunta no tiene intención de referirse a las llamadas a funciones realizadas para el propósito de inicialización. Mi pregunta es ¿Divide una tarea determinada en pequeñas funciones de varias líneas ( por lo que la línea está fuera de cuestión ) que se ejecuta dentro de un bucle infinito tiene alguna ventaja sobre la escritura de una función larga sin ninguna función anidada?
Considere la legibilidad definida en la respuesta de @Jonk .
fuente
Respuestas:
Podría decirse que, en su ejemplo, el rendimiento no importaría, ya que el código solo se ejecuta una vez al inicio.
Una regla general que uso: escriba su código lo más legible posible y solo comience a optimizar si nota que su compilador no está haciendo su magia correctamente.
El costo de una llamada de función en un ISR podría ser el mismo que el de una llamada de función durante el inicio en términos de almacenamiento y sincronización. Sin embargo, los requisitos de tiempo durante ese ISR podrían ser mucho más críticos.
Además, como ya han notado otros, el costo (y el significado del 'costo') de una llamada de función difiere según la plataforma, el compilador, la configuración de optimización del compilador y los requisitos de la aplicación. Habrá una gran diferencia entre un 8051 y un cortex-m7, y un marcapasos y un interruptor de luz.
fuente
No se me ocurre ninguna ventaja (pero vea la nota a JasonS en la parte inferior), envolviendo una línea de código como una función o subrutina. Excepto tal vez que pueda nombrar la función algo "legible". Pero también puedes comentar la línea. Y dado que concluir una línea de código en una función cuesta memoria de código, espacio de pila y tiempo de ejecución, me parece que es principalmente contraproducente. En una situación de enseñanza? Puede tener sentido. Pero eso depende de la clase de estudiantes, su preparación previa, el plan de estudios y el maestro. Sobre todo, creo que no es una buena idea. Pero esa es mi opinión.
Lo que nos lleva a la conclusión. Su amplia área de preguntas ha sido, durante décadas, un tema de debate y sigue siendo hoy en día un tema de debate. Entonces, al menos mientras leo su pregunta, me parece una pregunta basada en la opinión (como usted la hizo).
Se podría alejar de ser tan basado en la opinión como es, si fuera más detallado sobre la situación y describiera cuidadosamente los objetivos que tenía como primarios. Cuanto mejor defina sus herramientas de medición, más objetivas serán las respuestas.
En términos generales, desea hacer lo siguiente para cualquier codificación. (Para más adelante, supondré que estamos comparando diferentes enfoques, todos los cuales logran los objetivos. Obviamente, cualquier código que no realice las tareas necesarias es peor que el código que tiene éxito, independientemente de cómo se escriba).
Lo anterior es generalmente cierto sobre toda la codificación. No discutí el uso de parámetros, variables globales locales o estáticas, etc. La razón es que para la programación incrustada, el espacio de la aplicación a menudo impone nuevas restricciones extremas y muy significativas y es imposible discutir todas ellas sin discutir cada aplicación incrustada. Y eso no está sucediendo aquí, de todos modos.
Estas restricciones pueden ser cualquiera (y más) de estas:
Y así. (El código de cableado para instrumentación médica vital también tiene un mundo entero propio).
El resultado aquí es que la codificación incrustada a menudo no es algo gratuito, donde puede codificar como lo haría en una estación de trabajo. A menudo hay razones severas y competitivas para una amplia variedad de restricciones muy difíciles. Y estos pueden argumentar fuertemente en contra de las respuestas más tradicionales y comunes .
Con respecto a la legibilidad, encuentro que el código es legible si está escrito de una manera consistente que pueda aprender mientras lo leo. Y donde no hay un intento deliberado de ofuscar el código. Realmente no hay mucho más requerido.
El código legible puede ser bastante eficiente y puede cumplir con todos los requisitos anteriores que ya he mencionado. Lo principal es que comprenda completamente lo que produce cada línea de código que escribe a nivel de ensamblaje o máquina, a medida que lo codifica. C ++ pone una carga seria para el programador aquí porque hay muchas situaciones en las que fragmentos idénticos de código C ++ realmente generan fragmentos diferentes de código de máquina que tienen rendimientos muy diferentes. Pero C, en general, es principalmente un lenguaje de "lo que ves es lo que obtienes". Entonces es más seguro en ese sentido.
EDITAR por JasonS:
He estado usando C desde 1978 y C ++ desde aproximadamente 1987 y he tenido mucha experiencia usando ambos para mainframes, minicomputadoras y (principalmente) aplicaciones integradas.
Jason saca un comentario sobre el uso de 'en línea' como modificador. (En mi perspectiva, esta es una capacidad relativamente "nueva" porque simplemente no existió durante quizás la mitad de mi vida o más usando C y C ++.) El uso de funciones en línea puede hacer tales llamadas (incluso para una línea de código) bastante práctico. Y es mucho mejor, siempre que sea posible, que usar una macro debido a la tipificación que el compilador puede aplicar.
Pero también hay limitaciones. La primera es que no puede confiar en el compilador para "tomar la pista". Puede o no puede. Y hay buenas razones para no entender la indirecta. (Para un ejemplo obvio, si se toma la dirección de la función, esto requiere la creación de instancias de la función y el uso de la dirección para hacer la llamada ... requerirá una llamada. El código no se puede insertar en línea entonces). otras razones también. Los compiladores pueden tener una amplia variedad de criterios por los cuales juzgan cómo manejar la pista. Y como programador, esto significa que debesDedique un tiempo a aprender sobre ese aspecto del compilador o, de lo contrario, es probable que tome decisiones basadas en ideas erróneas. Por lo tanto, agrega una carga tanto al escritor del código como a cualquier lector y también a cualquiera que planee portar el código a otro compilador también.
Además, los compiladores C y C ++ admiten compilación separada. Esto significa que pueden compilar una pieza de código C o C ++ sin compilar ningún otro código relacionado para el proyecto. Para insertar código en línea, suponiendo que el compilador de otra manera pudiera elegir hacerlo, no solo debe tener la declaración "en alcance" sino que también debe tener la definición. Por lo general, los programadores trabajarán para asegurarse de que este sea el caso si están usando 'en línea'. Pero es fácil que los errores se cuelen.
En general, aunque también uso inline donde creo que es apropiado, tiendo a suponer que no puedo confiar en él. Si el rendimiento es un requisito importante, y creo que el OP ya ha escrito claramente que ha habido un impacto significativo en el rendimiento cuando se dirigieron a una ruta más "funcional", entonces ciertamente elegiría evitar confiar en línea como práctica de codificación y en su lugar, seguiría un patrón de escritura de código ligeramente diferente, pero completamente coherente.
Una nota final sobre 'en línea' y las definiciones están "dentro del alcance" para un paso de compilación separado. Es posible (no siempre confiable) que el trabajo se realice en la etapa de vinculación. Esto puede ocurrir si y solo si un compilador de C / C ++ oculta suficientes detalles en los archivos de objeto para permitir que un vinculador actúe sobre solicitudes 'en línea'. Personalmente, no he experimentado un sistema de enlace (fuera de Microsoft) que admita esta capacidad. Pero puede ocurrir. Una vez más, si se debe confiar o no dependerá de las circunstancias. Pero generalmente asumo que esto no se ha incluido en el enlazador, a menos que sepa lo contrario en base a buena evidencia. Y si confío en ello, se documentará en un lugar destacado.
C ++
Para aquellos interesados, aquí hay un ejemplo de por qué sigo siendo bastante cauteloso con C ++ al codificar aplicaciones integradas, a pesar de su disponibilidad actual. Lanzaré algunos términos que creo que todos los programadores de C ++ integrados deben saber en frío :
Eso es solo una lista corta. Si aún no sabe todo acerca de esos términos y por qué los enumeré (y muchos más que no enumeré aquí), le aconsejaría que no use C ++ para el trabajo incorporado, a menos que no sea una opción para el proyecto .
Echemos un vistazo rápido a la semántica de excepciones de C ++ para obtener solo un sabor.
El compilador de C ++ ve la primera llamada a foo () y solo puede permitir que se produzca un desenlace de activación normal, si foo () arroja una excepción. En otras palabras, el compilador de C ++ sabe que no se necesita ningún código adicional en este punto para admitir el proceso de desenrollado de trama involucrado en el manejo de excepciones.
Pero una vez que se ha creado String s, el compilador de C ++ sabe que debe destruirse correctamente antes de que se pueda permitir un desenrollado de trama, si se produce una excepción más adelante. Entonces, la segunda llamada a foo () es semánticamente diferente de la primera. Si la segunda llamada a foo () arroja una excepción (que puede o no puede hacer), el compilador debe haber colocado un código diseñado para manejar la destrucción de String s antes de permitir que se produzca el desenrollado habitual del marco. Esto es diferente al código requerido para la primera llamada a foo ().
(Es posible agregar decoraciones adicionales en C ++ para ayudar a limitar este problema. Pero el hecho es que los programadores que usan C ++ simplemente deben ser mucho más conscientes de las implicaciones de cada línea de código que escriben).
A diferencia de Malloc de C, el nuevo C ++ usa excepciones para señalar cuando no puede realizar la asignación de memoria sin procesar. También lo hará 'dynamic_cast'. (Consulte la tercera edición de Stroustrup, The C ++ Programming Language, páginas 384 y 385 para ver las excepciones estándar en C ++.) Los compiladores pueden permitir que este comportamiento se deshabilite. Pero, en general, incurrirá en una sobrecarga debido a los prólogos y epílogos de manejo de excepciones correctamente formados en el código generado, incluso cuando las excepciones realmente no tienen lugar e incluso cuando la función que se está compilando en realidad no tiene ningún bloque de manejo de excepciones. (Stroustrup lo ha lamentado públicamente).
Sin una especialización parcial de la plantilla (no todos los compiladores de C ++ lo admiten), el uso de plantillas puede significar un desastre para la programación integrada. Sin él, la floración de código es un riesgo grave que podría matar un proyecto incrustado de memoria pequeña en un instante.
Cuando una función C ++ devuelve un objeto, se crea y destruye un compilador temporal sin nombre. Algunos compiladores de C ++ pueden proporcionar un código eficiente si se usa un constructor de objetos en la declaración de retorno, en lugar de un objeto local, reduciendo las necesidades de construcción y destrucción en un objeto. Pero no todos los compiladores hacen esto y muchos programadores de C ++ ni siquiera son conscientes de esta "optimización del valor de retorno".
Proporcionar un constructor de objetos con un tipo de parámetro único puede permitir que el compilador de C ++ encuentre una ruta de conversión entre dos tipos de maneras completamente inesperadas para el programador. Este tipo de comportamiento "inteligente" no es parte de C.
Una cláusula catch que especifique un tipo base "dividirá" un objeto derivado lanzado, porque el objeto lanzado se copia utilizando el "tipo estático" de la cláusula catch y no el "tipo dinámico" del objeto. Una fuente no común de miseria de excepción (cuando siente que incluso puede permitirse excepciones en su código incrustado).
Los compiladores de C ++ pueden generar automáticamente constructores, destructores, constructores de copias y operadores de asignación para usted, con resultados no deseados. Lleva tiempo ganar facilidad con los detalles de esto.
Pasar matrices de objetos derivados a una función que acepta matrices de objetos base, rara vez genera advertencias del compilador, pero casi siempre produce un comportamiento incorrecto.
Dado que C ++ no invoca el destructor de objetos parcialmente construidos cuando se produce una excepción en el constructor de objetos, el manejo de excepciones en los constructores generalmente exige "punteros inteligentes" para garantizar que los fragmentos construidos en el constructor se destruyan adecuadamente si se produce una excepción allí . (Consulte Stroustrup, página 367 y 368.) Este es un problema común al escribir buenas clases en C ++, pero, por supuesto, se evita en C ya que C no tiene la semántica de construcción y destrucción incorporada. Escribir el código adecuado para manejar la construcción de subobjetos dentro de un objeto significa escribir código que debe hacer frente a este problema semántico único en C ++; en otras palabras, "escribir alrededor" de los comportamientos semánticos de C ++.
C ++ puede copiar objetos pasados a parámetros de objeto. Por ejemplo, en los siguientes fragmentos, la llamada "rA (x);" puede hacer que el compilador de C ++ invoque un constructor para el parámetro p, para luego llamar al constructor de copia para transferir el objeto x al parámetro p, y luego otro constructor para el objeto de retorno (un temporario sin nombre) de la función rA, que por supuesto es copiado del parámetro p. Peor aún, si la clase A tiene sus propios objetos que necesitan construcción, esto puede telescopizar desastrosamente. (El programador de AC evitaría la mayor parte de esta basura, optimizando a mano ya que los programadores de C no tienen una sintaxis tan práctica y tienen que expresar todos los detalles uno por uno).
Finalmente, una breve nota para los programadores de C. longjmp () no tiene un comportamiento portátil en C ++. (Algunos programadores de C usan esto como una especie de mecanismo de "excepción".) Algunos compiladores de C ++ en realidad intentarán configurar las cosas para limpiar cuando se toma el longjmp, pero ese comportamiento no es portátil en C ++. Si el compilador limpia los objetos construidos, no es portátil. Si el compilador no los limpia, entonces los objetos no se destruyen si el código abandona el alcance de los objetos construidos como resultado de longjmp y el comportamiento no es válido. (Si el uso de longjmp en foo () no deja un alcance, entonces el comportamiento puede estar bien). Esto no es usado con demasiada frecuencia por los programadores incrustados en C, pero deben ser conscientes de estos problemas antes de usarlos.
fuente
inline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }
que se llama en numerosos lugares es un candidato perfecto.1) Código de legibilidad y mantenibilidad primero. El aspecto más importante de cualquier código base es que está bien estructurado. El software bien escrito tiende a tener menos errores. Es posible que deba realizar cambios en un par de semanas / meses / años, y es de gran ayuda si su código es agradable de leer. O tal vez alguien más tiene que hacer un cambio.
2) El rendimiento del código que se ejecuta una vez no importa mucho. Cuida el estilo, no el rendimiento
3) Incluso el código en bucles estrechos debe ser correcto en primer lugar. Si enfrenta problemas de rendimiento, optimice una vez que el código sea correcto.
4) Si necesita optimizar, ¡tiene que medir! No importa si piensas o alguien te dice que
static inline
es solo una recomendación para el compilador. Tienes que echar un vistazo a lo que hace el compilador. También debe medir si la alineación mejoró el rendimiento. En los sistemas integrados, también debe medir el tamaño del código, ya que la memoria de código suele ser bastante limitada. Esta es LA regla más importante que distingue la ingeniería de las conjeturas. Si no lo midió, no ayudó. La ingeniería es medir. La ciencia lo está escribiendo;)fuente
Cuando se llama a una función solo en un lugar (incluso dentro de otra función), el compilador siempre coloca el código en ese lugar en lugar de llamar realmente a la función. Si la función se llama en muchos lugares, entonces tiene sentido usar una función al menos desde el punto de vista del tamaño del código.
Después de compilar el código no tendrá las llamadas múltiples, en cambio, la legibilidad mejorará enormemente.
También querrá tener, por ejemplo, el código de inicio de ADC en la misma biblioteca con otras funciones de ADC que no están en el archivo c principal.
Muchos compiladores le permiten especificar diferentes niveles de optimización para la velocidad o el tamaño del código, por lo que si tiene una función pequeña llamada en muchos lugares, la función se "alineará", se copiará allí en lugar de llamar.
La optimización de la velocidad alineará las funciones en todos los lugares que pueda, la optimización del tamaño del código llamará a la función, sin embargo, cuando se llama a una función solo en un lugar, en su caso siempre estará "alineada".
Código como este:
se compilará para:
sin usar ninguna llamada.
Y la respuesta a su pregunta, en su ejemplo o similar, la legibilidad del código no afecta el rendimiento, no hay mucha velocidad o tamaño del código. Es común usar varias llamadas solo para que el código sea legible, al final se cumplen como un código en línea.
Actualice para especificar que las declaraciones anteriores no son válidas para compiladores de versión gratuita paralizados a propósito, como la versión gratuita Microchip XCxx. Este tipo de llamadas a funciones es una mina de oro para Microchip para mostrar cuánto mejor es la versión paga y si compila esto encontrará en el ASM exactamente las llamadas que tiene en el código C.
Tampoco es para programadores tontos que esperan usar el puntero a una función en línea.
Esta es la sección de electrónica, no la sección general de C C ++ o de programación, la pregunta es sobre la programación de microcontroladores donde cualquier compilador decente hará la optimización anterior por defecto.
Por lo tanto, deje de votar solo porque, en casos raros e inusuales, esto podría no ser cierto.
fuente
En primer lugar, no hay mejor o peor; Todo es cuestión de opinión. Tienes mucha razón en que esto es ineficiente. Se puede optimizar o no; depende. Por lo general, verá este tipo de funciones, reloj, GPIO, temporizador, etc. en archivos / directorios separados. Los compiladores generalmente no han sido capaces de optimizar estas brechas. Hay uno que puede conocer, pero que no se usa ampliamente para cosas como esta.
Archivo único:
Elegir un objetivo y un compilador con fines de demostración.
Esto es lo que la mayoría de las respuestas aquí le están diciendo, que es ingenuo y que todo esto se optimiza y se eliminan las funciones. Bueno, no se eliminan, ya que están definidos globalmente de forma predeterminada. Podemos eliminarlos si no es necesario fuera de este archivo.
los elimina ahora ya que están en línea.
Pero la realidad es cuando adquieres proveedores de chips o bibliotecas BSP,
Definitivamente comenzará a agregar gastos generales, lo que tiene un costo notable para el rendimiento y el espacio. De unos pocos a cinco por ciento de cada uno, dependiendo de cuán pequeña sea cada función.
¿Por qué se hace esto de todos modos? Parte de esto es el conjunto de reglas que los profesores enseñarían o aún enseñarían para facilitar el código de calificación. Las funciones deben caber en una página (cuando imprimiste tu trabajo en papel), no hagas esto, no hagas eso, etc. Mucho de esto es hacer bibliotecas con nombres comunes para diferentes objetivos. Si tiene decenas de familias de microcontroladores, algunos de los cuales comparten periféricos y otros no, tal vez tres o cuatro diferentes sabores UART mezclados entre las familias, diferentes GPIO, controladores SPI, etc. Puede tener una función genérica gpio_init (), get_timer_count (), etc. Y reutilice esas abstracciones para los diferentes periféricos.
Se convierte principalmente en un caso de mantenimiento y diseño de software, con cierta legibilidad posible. Mantenibilidad, legibilidad y rendimiento que no puede tener todo; solo puedes elegir uno o dos a la vez, no los tres.
Esta es una pregunta basada en la opinión, y lo anterior muestra las tres formas principales en que esto puede ir. En cuanto a qué camino es MEJOR, eso es estrictamente opinión. ¿Está haciendo todo el trabajo en una función? Una pregunta basada en la opinión, algunas personas se inclinan por el rendimiento, algunas definen la modularidad y su versión de legibilidad como la MEJOR. El tema interesante con lo que mucha gente llama legibilidad es extremadamente doloroso; para "ver" el código, debe tener 50-10,000 archivos abiertos a la vez y de alguna manera tratar de ver linealmente las funciones en orden de ejecución para ver lo que está sucediendo. Creo que es lo opuesto a la legibilidad, pero a otros les resulta legible ya que cada elemento cabe en la ventana de la pantalla / editor y puede consumirse en su totalidad después de haber memorizado las funciones que se están llamando y / o tener un editor que puede entrar y salir de cada función dentro de un proyecto.
Ese es otro factor importante cuando ves varias soluciones. Los editores de texto, IDEs, etc. son muy personales y van más allá de vi vs Emacs. Eficiencia de programación, las líneas por día / mes aumentan si se siente cómodo y eficiente con la herramienta que está utilizando. Las características de la herramienta pueden / se inclinarán intencionalmente o no hacia la forma en que los fanáticos de esa herramienta escriben el código. Y como resultado, si un individuo está escribiendo estas bibliotecas, el proyecto en cierta medida refleja estos hábitos. Incluso si se trata de un equipo, el desarrollador principal o los hábitos / preferencias del jefe pueden verse obligados al resto del equipo.
Estándares de codificación que tienen muchas preferencias personales enterradas, vi muy religioso contra Emacs nuevamente, pestañas versus espacios, cómo se alinean los corchetes, etc. Y estos juegan en cómo las bibliotecas están diseñadas hasta cierto punto.
¿Cómo deberías escribir el tuyo? Como quieras, realmente no hay una respuesta incorrecta si funciona. Hay un código malo o arriesgado, pero si está escrito de tal manera que pueda mantenerlo según sea necesario, cumple con sus objetivos de diseño, renuncia a la legibilidad y algo de mantenimiento si el rendimiento es importante, o viceversa. ¿Te gustan los nombres cortos de variables para que una sola línea de código se ajuste al ancho de la ventana del editor? O nombres demasiado largos y descriptivos para evitar confusiones, pero la legibilidad disminuye porque no se puede obtener una línea en una página; ahora está visualmente dividido, jugando con el flujo.
No vas a pegar un jonrón la primera vez al bate. Puede / debería tomar décadas definir realmente su estilo. Al mismo tiempo, durante ese tiempo, su estilo puede cambiar, inclinarse hacia un lado por un tiempo y luego inclinarse hacia otro.
Vas a escuchar mucho de no optimizar, nunca optimizar y optimización prematura. Pero como se muestra, los diseños como este desde el principio crean problemas de rendimiento, luego comienzas a ver hacks para resolver ese problema en lugar de rediseñar desde el principio para realizar. Estoy de acuerdo en que hay situaciones, una sola función, unas pocas líneas de código que puede tratar de manipular con dificultad el compilador basándose en el temor de lo que el compilador hará de otra manera (tenga en cuenta que con experiencia este tipo de codificación se vuelve fácil y natural, optimizando a medida que escribe sabiendo cómo compilará el código el compilador), entonces desea confirmar dónde está realmente el ladrón de ciclos, antes de atacarlo.
También debe diseñar su código para el usuario hasta cierto punto. Si este es su proyecto, usted es el único desarrollador; es lo que quieras Si está tratando de hacer una biblioteca para regalar o vender, probablemente quiera hacer que su código se vea como todas las demás bibliotecas, cientos de miles de archivos con funciones pequeñas, nombres largos de funciones y nombres largos de variables. A pesar de los problemas de legibilidad y problemas de rendimiento, IMO encontrará que más personas podrán usar ese código.
fuente
Regla muy general: el compilador puede optimizar mejor que usted. Por supuesto, hay excepciones si está haciendo cosas muy intensivas en bucle, pero en general si desea una buena optimización para la velocidad o el tamaño del código, elija sabiamente su compilador.
fuente
De seguro depende de su propio estilo de codificación. Una regla general que existe es que los nombres de las variables, así como los nombres de las funciones, deben ser lo más claros y explicativos posibles. Cuantas más sub-llamadas o líneas de código pongas en una función, más difícil será definir una tarea clara para esa función. En su ejemplo, tiene una función
initModule()
que inicializa cosas y llama a subrutinas que luego configuran el reloj o configuran la configuración . Puedes decir eso simplemente leyendo el nombre de la función. Si coloca todo el código de las subrutinas en suinitModule()
directamente, se vuelve menos obvio lo que realmente hace la función. Pero como a menudo, es solo una guía.fuente
Si una función realmente solo hace una cosa muy pequeña, considere hacerla
static inline
.Agréguelo a un archivo de encabezado en lugar del archivo C y use las palabras
static inline
para definirlo:Ahora, si la función es incluso un poco más larga, como tener más de 3 líneas, podría ser una buena idea evitar
static inline
y agregarla al archivo .c. Después de todo, los sistemas integrados tienen memoria limitada y no desea aumentar demasiado el tamaño del código.Además, si define la función
file1.c
y la usafile2.c
, el compilador no la alineará automáticamente. Sin embargo, si lo definefile1.h
comostatic inline
función, es probable que su compilador lo incorpore.Estas
static inline
funciones son extremadamente útiles en la programación de alto rendimiento. He descubierto que a menudo aumentan el rendimiento del código en un factor de más de tres.fuente
file1.c
y la usafile2.c
, el compilador no la alineará automáticamente. Falso . Ver, por ejemplo,-flto
en gcc o clang.Una dificultad al intentar escribir código eficiente y confiable para microcontroladores es que algunos compiladores no pueden manejar ciertas semánticas de manera confiable a menos que el código use directivas específicas del compilador o inhabilite muchas optimizaciones.
Por ejemplo, si tiene un sistema de un solo núcleo con una rutina de servicio de interrupción [ejecutada por un tic del temporizador o lo que sea]:
Debería ser posible escribir funciones para iniciar una operación de escritura en segundo plano o esperar a que se complete:
y luego invoque dicho código usando:
Desafortunadamente, con las optimizaciones completas habilitadas, un compilador "inteligente" como gcc o clang decidirá que no hay forma de que el primer conjunto de escrituras pueda tener algún efecto en la observabilidad del programa y, por lo tanto, pueden optimizarse. Compiladores de calidad como
icc
son menos propensos a hacer esto si el acto de establecer una interrupción y esperar la finalización involucra tanto escrituras volátiles como lecturas volátiles (como es el caso aquí), pero la plataforma dirigida poricc
no es tan popular para los sistemas integrados.El Estándar ignora deliberadamente los problemas de calidad de implementación, y considera que hay varias formas razonables de manejar la construcción anterior:
A implementaciones de calidad destinadas exclusivamente a campos como el procesamiento de números de alta gama podría razonablemente esperar que el código escrito para tales campos no contenga construcciones como las anteriores.
Una implementación de calidad puede tratar todos los accesos a los
volatile
objetos como si pudieran desencadenar acciones que accederían a cualquier objeto que sea visible para el mundo exterior.Una implementación simple pero de calidad decente destinada al uso de sistemas integrados podría tratar todas las llamadas a funciones no marcadas como "en línea" como si pudieran acceder a cualquier objeto que haya estado expuesto al mundo exterior, incluso si no se trata
volatile
como se describe en # 2)La Norma no intenta sugerir cuál de los enfoques anteriores sería el más apropiado para una implementación de calidad, ni para exigir que las implementaciones "conformes" sean de una calidad suficientemente buena como para ser utilizables para un propósito particular. En consecuencia, algunos compiladores como gcc o clang efectivamente requieren que cualquier código que quiera usar este patrón se compile con muchas optimizaciones deshabilitadas.
En algunos casos, asegurarse de que las funciones de E / S están en una unidad de compilación separada y un compilador no tendrá más remedio que asumir que podrían acceder a cualquier subconjunto arbitrario de objetos que hayan estado expuestos al mundo exterior puede ser un mínimo razonable. forma de escribir código que funcionará de manera confiable con gcc y clang. En tales casos, sin embargo, el objetivo no es evitar el costo adicional de una llamada de función innecesaria, sino aceptar el costo que debería ser innecesario a cambio de obtener la semántica requerida.
fuente
volatile
accesos como si pudieran desencadenar potencialmente accesos arbitrarios a otros objetos), pero por cualquier razón gcc y clang preferirían tratar los problemas de calidad de implementación como una invitación a comportarse de manera inútil.buff
no se declaravolatile
, no se tratará como una variable volátil, los accesos pueden ser reordenados u optimizados por completo si aparentemente no se usan más tarde. La regla es simple: marque todas las variables a las que se pueda acceder fuera del flujo normal del programa (como lo ve el compilador) comovolatile
. ¿Sebuff
accede al contenido de un controlador de interrupciones? Si. Entonces debería servolatile
.magic_write_count
es cero, el almacenamiento es propiedad de la línea principal. Cuando no es cero, es propiedad del controlador de interrupciones. Haciendobuff
volátil requeriría que todas las funciones en cualquier lugar que opera sobre ella utilizavolatile
punteros Calificado, que puedan menoscabar la optimización mucho más que tener un compilador ...