El problema
Estoy haciendo un poco de codificación de firmware y tengo muchas máscaras de bits para administrar. Hasta ahora, he estado repitiendo el mismo bloque para cada campo que quiero enmascarar de la siguiente manera:
#define LinkStateMask 0x1 #define LinkStateShift 8 #define LinkState(x) (((x) >> LinkStateShift) & LinkStateMask)
Tenga en cuenta que los campos suelen ser campos de varios bits, por lo que no siempre se garantiza que la máscara sea 0x1
. La macro final se puede usar en el código de manera bastante legible y esto es algo que me gusta bastante:
if( LinkState(temp) == 0 ) { // Link is down, return error return -ENODEV; }
La pregunta
¿Hay alguna forma de que el preprocesador C genere estas macros por mí? Sé que el preprocesador solo funciona en un solo paso, por lo que una macro no puede definir directamente a otra, pero espero que haya otra forma.
Idealmente, me gustaría escribir algo similar a lo siguiente y usar el mismo código C que mi segunda lista anterior:
BIT_FIELD(LinkState, 8, 0x1) // BIT_FIELD(name, lsb, mask)
La clave para mí es mantener el uso de estilo de función con nombre simbólico en el archivo C principal sin tener que escribir la invocación completa BIT_FIELD(...)
cada vez que necesito generar una máscara (algunos de estos campos se usan ampliamente, lo que lleva a una pesadilla de mantenimiento).
Una restricción final: los campos que necesito están escasamente dispersos en cientos de registros. Si es posible, realmente me gustaría evitar tener que definir una struct
para cada uno, ya que normalmente solo necesito uno o dos campos de cada uno.
Una macro no puede definir otra macro, pero puede llamar a otra (el preprocesador no se detendrá hasta que no queden no terminales)
Entonces, ¿qué tal algo como esto? al menos se ahorra algo de tecleo...
#include <stdio.h> #define SHIFTMASK(x,s,m) (((x) >> (s)) & (m)) #define LinkState(x) SHIFTMASK(x, 8, 0x1) #define Whatever(x) SHIFTMASK(x, 9, 0x7) int test[] = { 0x0f00, 0x0a01, 0x0100, 0x0007 }; int main() { int i; for (i = 0; i < 4; ++i) { printf("test[%d]: 0x%x\n", i, test[i]); printf("LinkState(test[%d]): 0x%x\n", i, LinkState(test[i])); printf("Whatever(test[%d]): 0x%x\n", i, Whatever(test[i])); } return 0; }
Como sugirió Art , una posibilidad es usar una macro para crear una función en línea:
#define BIT_FIELD(name, lsb, mask) \ static inline int name(int value) { return (value >> (lsb)) & (mask); }
Los paréntesis son necesarios para lsb
y mask
, en caso de que a alguien le guste la invocación.
A continuación, puede crear funciones según sea necesario:
BIT_FIELD(LinkState, 8, 0x1)
e invocarlos cuando sea necesario:
int x = LinkState(y);
La definición de la macro y las invocaciones pueden ir en un encabezado, aunque no pasa nada si hay invocaciones locales a un archivo específico.
¿Hay algún impacto importante en el rendimiento al hacer esto como una función en lugar de hacerlo como un valor precalculado?
Es poco probable que haya un impacto en el rendimiento porque, para una función tan simple, el compilador alineará todo el código, exactamente como si hubiera usado una macro y sin sobrecarga de funciones. Sí, existe la posibilidad de que su compilador esté dañado (o no sea compatible con C99 o C11; se agregó en inline
a C99). Es tentador decir "consigue un mejor compilador". Pruebe, mida, pero no debe haber gastos generales.
Si eso es realmente un problema, entonces debe recurrir a alternativas indirectas. Por ejemplo, podría crear un archivo bitfields.hdr
que contenga:
#define BIT_HASH # #define BIT_FIELD(name, lsb, mask) \ BIT_HASH define name(value) (((value) >> (lsb)) & (mask)) BIT_FIELD(LinkState, 8, 0x1)
A continuación, puede hacer arreglos para compilarlo en un archivo de encabezado:
cpp -E bitfield.hdr > bitfield.h
Con el cpp
de GCC 5.1.0, obtengo el resultado:
# 1 "bitfield.hdr" # 1 "<built-in>" # 1 "<command-line>" # 1 "bitfield.hdr" # define LinkState(value) (((value) >> (8)) & (0x1))
Luego puede usar #include "bitfield.h"
en el código de trabajo. Esta es una forma simple de generación de código. Si lo prefiere, puede utilizar un procesador de macros alternativo; m4
sería la alternativa obvia, pero podría usar cualquier cosa que le guste ( awk
, perl
, python
, ruby
, cualquiera de ellos podría usarse).
O puede hacer lo que sugiere Felix Palmen , que es mejor y más simple que precompilar como se sugiere. Me sentiría tentado a usar las funciones en línea siempre que funcionen como se espera, pero la sugerencia de Félix es qué usar si no funcionan.
Otro truco, que podría ser incluso mejor, es definir campos de bits usando un operador ternario.
Estas macros manejan la interpretación:
// Extract starting and ending bit number from a bit_range, // defined as the arguments for a ternary operator (low:high). // Example : #define Least3Bits 0:2 #define BitRange(StartBit, BitCount) (StartBit):(StartBit+BitCount-1) #define BitRangeStart(bit_range) (1?bit_range) #define BitRangeEnd(bit_range) (0?bit_range) #define BitRangeMask(bit_range) ((~0 << BitRangeStart(bit_range)) ^ (~0 << BitRangeEnd(bit_range))) #define BitRangeShift(bit_range) BitRangeStart(bit_range) #define BitRangeValue(bit_range, value) (((value) & BitRangeMask(bit_range)) >> BitRangeShift(bit_range))
Ahora su ejemplo se puede declarar así:
#define LinkState_BitRange BitRange(8, 1) #define LinkState(value) BitRangeValue(LinkState_BitRange, value)
Y el otro ejemplo:
#define Whatever_BitRange BitRange(9, 3) #define Whatever(x) BitRangeValue(Whatever_BitRange, value)
Declarar los rangos de bits incorrectamente conducirá a errores de compilación, por lo que este es un método bastante seguro de usar también.