2009-08-06 14 views
9

Estoy trabajando en algún código C++ para un sistema integrado. La interfaz de E/S que utiliza el código requiere que el tamaño de cada mensaje (en bytes) sea una potencia de dos. En este momento, el código hace algo como esto (en varios lugares):Rellenar una estructura C++ con una potencia de dos

#pragma pack(1) 
struct Message 
{ 
    struct internal_ 
    { 
     unsigned long member1; 
     unsigned long member2; 
     unsigned long member3; 
     /* more members */ 
    } internal; 
    char pad[64-sizeof(internal_)]; 
}; 
#pragma pack() 

Estoy intentando compilar el código en una de 64 bits de Fedora, por primera vez, donde long es de 64-bits. En este caso, sizeof(internal_) es mayor que 64, la expresión de tamaño de matriz se desborda y el compilador se queja de que la matriz es demasiado grande.

Idealmente, me gustaría poder escribir una macro que tome el tamaño de la estructura y evaluar en tiempo de compilación el tamaño requerido de la matriz de relleno para redondear el tamaño de la estructura a una potencia de dos.

He consultado la página Bit Twiddling Hacks, pero no sé si alguna de las técnicas allí realmente puede implementarse en una macro para ser evaluada en tiempo de compilación.

¿Alguna otra solución a este problema? ¿O debería perpetuar el problema y simplemente cambiar el mágico 64 a un mágico 128?

Respuesta

15

Usa un metaprograma de plantilla. (Editado en respuesta al comentario).

#include <iostream> 
#include <ostream> 
using namespace std; 

template <int N> 
struct P 
{ 
    enum { val = P<N/2>::val * 2 }; 
}; 

template <> 
struct P<0> 
{ 
    enum { val = 1 }; 
}; 

template <class T> 
struct PadSize 
{ 
    enum { val = P<sizeof (T) - 1>::val - sizeof (T) }; 
}; 

template <class T, int N> 
struct PossiblyPadded 
{ 
    T  payload; 
    char pad[N]; 
}; 

template <class T> 
struct PossiblyPadded<T, 0> 
{ 
    T  payload; 
}; 

template <class T> 
struct Holder : public PossiblyPadded<T, PadSize<T>::val> 
{ 
}; 


int main() 
{ 
    typedef char Arr[6]; 

    Holder<Arr> holder; 
    cout << sizeof holder.payload << endl; 

    // Next line fails to compile if sizeof (Arr) is a power of 2 
    // but holder.payload always exists 
    cout << sizeof holder.pad << endl; 
} 
+0

Me gusta esto, pero falla cuando el contenido es una potencia par de 2. La declaración de pad obtiene error C2466: no se puede asignar una matriz de tamaño constante 0 –

+0

Buen punto. Corregido ahora. – fizzer

+1

Supongo que puede derivar PossiblyPadded de T en lugar de tener un miembro T (para T adecuado) y ahorrarse la molesta sintaxis de acceso de miembro. – fizzer

5

¿Por qué no utilizar una unión?

union Message 
{ 
    struct internal_ 
    { 
     unsigned long member1; 
     /* more members */ 
    }; 
    char[64]; 
}; 

o mejor aún, utilizar estructuras anónimas

union Message 
{ 
    struct 
    { 
     unsigned long member1; 
     /* more members */ 
    }; 
    char[64]; 
}; 

Así se puede acceder a los miembros de esta manera: Message.member1;

Editar: obviamente esto no resuelve su problema mayor que 64, pero proporciona una manera más limpia de relleno.

+4

Una buena sugerencia, pero no responde a la pregunta ... – bdonlan

+0

En realidad esto es peligroso, ya que miembro1 y member2 NO estarán alineados en la misma dirección en diferentes plataformas. Como este es el código IO, esto generaría errores. –

6

Probablemente la forma más obvia sería que sólo tiene que utilizar el operador ternario:

#define LOG2_CONST(n) ((n) <= 1 ? 0 : 
         ((n) <= 2 ? 1 : 
         ((n) <= 4 ? 2 : 
         /* ... */ 
        )))))))))))))))))))))))))))))) 
#define PADDED_STRUCT(ResultName, BaseName) \ 
    typedef union { BaseName data; char pad[1 << LOG2_CONST(sizeof(BaseName))]; } ResultName 
4

Una forma de trabajar alrededor del problema sería la de sustituir el codificado 64 con un múltiplo del tamaño (largo), convirtiendo el relleno en algo como esto:

char pad[4*sizeof(unsigned long) - sizeof(internal_)]; 

Es feo pero debería ser portátil a 64 bits.

Dicho esto, una API que requiere que el tamaño del mensaje sea una potencia de 2 suena un poco extraño y como un problema de diseño. Exigir que el tamaño sea un número par tiene sentido, como en algunos procesadores, pagar una multa considerable por acceder a datos en direcciones impares, pero tu paquete #pragma casi lo hace inevitable.

4

¿Qué tal si escribimos un pequeño envoltorio alrededor de la función enviar y recibir mensajes que maneja cualquier tamaño de mensaje y simplemente asignan un búfer más grande (el siguiente poder de 2) y lo limitan, copian la estructura al principio y lo envían .

0
union Message 
{ 
    struct 
    { 
     unsigned long member1; 
     unsigned long member2; //... 
    }; 
    char pad[1 << 5]; //change 5 to whatever size you need... 
}; 

Sería un poco más limpio.

0

me gusta Niki's answer, especialmente la parte con estructuras anónimas.

Una cosa que la respuesta no resolvió fue la mayor que la 64-bytes problema, pero que puede ser resuelto por la que se declara condicionalmente un char [128] miembro de estructura si sizeof == (largo) 8 y declarar char [64] en caso contrario.

1

Usted puede obtener una constante de la dimensión de la estructura en tiempo de compilación redondea a una potencia de dos que utilizan plantillas:

template<int N, int C = 1> 
struct Recurse 
{ 
    enum {result = Recurse<N/2, C*2>::result}; 
}; 

template<int C> 
struct Recurse<0, C> 
{ 
    enum {result = C}; 
}; 

template<typename T> 
struct Calc 
{ 
    enum {size = Recurse<sizeof(Test)-1>::result}; 
}; 

struct Test 
{ 
    int a; 
    double b; 
    double c; 
}; 

int main() 
{ 
    std::cout << sizeof(Test) << " -> " << Calc<Test>::size << std::endl; 
    return 0; 
} 

El valor de relleno debe entonces ser fácil.

0

Y sin embargo, otra solución plantilla (robar enormemente de fizzer):

#include <iostream> 
#include <ostream> 
using namespace std; 

template <int TSize, int PSize = 1, bool = false> 
struct PadSize 
{ 
    static const int val = 
    (PadSize < TSize, (PSize*2), (TSize <= (PSize*2) ) > :: val); 
}; 

template < int TSize, int PSize> 
struct PadSize <TSize, PSize, true> // As soon as TSize is <= to PSize 
{ 
    static const int val = PSize; 
}; 

int main() 
{ 
    typedef char Arr[8]; 
    char pad[ PadSize <sizeof(Arr)>::val ]; 

    cout << sizeof pad << endl; 
} 

Mi enfoque es simplemente para mantener duplicar el tamaño de relleno hasta que sea al menos tan grande como el tamaño de letra.

2

Ya no usas el (los) compilador (es) que usas específicamente, pero deberías ver si soportan argumentos para el paquete que controla la alineación/relleno y luego puedes deshacerte del campo de relleno por completo. Sé que MSVC's versión de pragma pack es compatible con esto, al igual que GCC's.

2

Puede macroize this de la siguiente manera (para la arquitectura de 32 bits):

#define align_step(N, shift) ((N) | ((N) >> shift)) 
#define align_up(N) (align_step(align_step(align_step(align_step(align_step((N)-1, 1), 2), 4), 8), 16) + 1) 
#define alignment_padding(N) (align_up((N)) - (N)) 

entonces usted puede solicitar que el uso de la unión truco o algún otro medio. En su ejemplo:

#pragma pack(1) 
struct Message 
{ 
    struct internal_ 
    { 
     unsigned long member1; 
     unsigned long member2; 
     unsigned long member3; 
     /* more members */ 
    } internal; 
    char pad[alignment_padding(sizeof(internal_))]; 
}; 
#pragma pack() 
+0

Me gusta esto, no había pensado utilizar una macro para tratar el n repetido | n >> 1,2,4,8 ... etc. –

Cuestiones relacionadas