2010-03-02 24 views
31

Estoy sobrecargando operator new, pero recientemente tuve un problema con la alineación. Básicamente, tengo una clase IBase que proporciona operator new y delete en todas las variantes requeridas. Todas las clases derivan de IBase y, por lo tanto, también usan los asignadores personalizados.operador nueva sobrecarga y alineación

El problema que estoy enfrentando ahora es que tengo un hijo Foo que tiene que estar alineado 16 bytes, mientras que todos los demás están bien cuando se alinean a 8 bytes. Sin embargo, mi asignador de memoria se alinea con los límites de 8 bytes de forma predeterminada, por lo que ahora el código en IBase::operator new devuelve una pieza de memoria inutilizable. ¿Cómo se supone que esto se resuelva correctamente?

Puedo simplemente forzar todas las asignaciones a 16 bytes, lo que funcionará bien hasta que aparezca un tipo alineado de 32 bytes. Averiguar la alineación dentro de operator new no parece ser trivial (¿puedo hacer una llamada de función virtual allí para obtener la alineación real?) ¿Cuál es la forma recomendada de manejar esto?

Sé que se supone que malloc devuelve una pieza de memoria que está adecuadamente alineada para todo, desafortunadamente, este "todo" no incluye tipos de SSE y realmente me gustaría hacerlo funcionar sin requerir que el usuario recuerde qué tipo tiene qué alineación.

+0

Quizás podría utilizar un IBase (IBase16?) Diferente para los pocos objetos que tienen requisitos de alineación especiales. –

+0

Incluso podría crear esta clase base (IBase16, IBase32) con plantilla, por lo que podría usar IBase . – Patrick

+0

Puede alinear los límites de 64 bytes. :) – Bill

Respuesta

20

Esta es una posible solución. Siempre va a elegir el operador con la mayor alineación en una jerarquía determinada:

#include <exception> 
#include <iostream> 
#include <cstdlib> 

// provides operators for any alignment >= 4 bytes 
template<int Alignment> 
struct DeAllocator; 

template<int Alignment> 
struct DeAllocator : virtual DeAllocator<Alignment/2> { 
    void *operator new(size_t s) throw (std::bad_alloc) { 
    std::cerr << "alignment: " << Alignment << "\n"; 
    return ::operator new(s); 
    } 

    void operator delete(void *p) { 
    ::operator delete(p); 
    } 
}; 

template<> 
struct DeAllocator<2> { }; 

// ........... Test ............. 
// different classes needing different alignments 
struct Align8 : virtual DeAllocator<8> { }; 
struct Align16 : Align8, virtual DeAllocator<16> { }; 
struct DontCare : Align16, virtual DeAllocator<4> { }; 

int main() { 
    delete new Align8; // alignment: 8 
    delete new Align16; // alignment: 16 
    delete new DontCare; // alignment: 16 
} 

Se basa en la regla de predominio: Si hay una ambigüedad en las operaciones de búsqueda y la ambigüedad es entre los nombres de un derivado y una base virtual clase, el nombre de la clase derivada se toma en su lugar.


Preguntas por qué se habían alzado DeAllocator<I> hereda DeAllocator<I/2>. La respuesta es porque en una jerarquía dada, puede haber diferentes requisitos de alineación impuestos por las clases. IBase imaginar que no tiene requisitos de alineación, A tiene requerimiento de 8 bytes y B tiene 16 bytes requisito y hereda A:

class IBAse { }; 
class A : IBase, Alignment<8> { }; 
class B : A, Alignment<16> { }; 

Alignment<16> y Alignment<8> tanto exponer una operator new. Si ahora dice new B, el compilador buscará operator new en B y encontrará dos funciones:

  // op new 
      Alignment<8>  IBase 
       ^  /
        \  /
        \ /
// op new   \/
Alignment<16>   A 
      \  /
       \ /
       \/
       B 

B ->  Alignment<16> -> operator new 
B -> A -> Alignment<8> -> operator new 

Por lo tanto, esto sería ambigua y que fallaría para compilar: Ninguno de ellos ocultan la otra uno. Pero si ahora hereda Alignment<16> prácticamente desde Alignment<8> y hacer A y B hereda virtualmente, el operator new en Alignment<8> estarán ocultos:

  // op new 
      Alignment<8>  IBase 
       ^  /
       /\  /
      / \ /
// op new/  \/
Alignment<16>   A 
      \  /
       \ /
       \/
       B 

Esta regla escondite especial (también llamada predominio regla) sin embargo, sólo funciona si todos los objetosAlignment<8> son iguales. Por lo tanto, siempre heredamos virtualmente: en ese caso, solo existe unAlignment<8> (o 16, ...) objeto existente en cualquier jerarquía de clases dada.

+0

Buen uso de plantillas y me gusta tu forma de pensar, pero me falta algo obvio ya que no veo cómo se está haciendo la alineación. Se supone que las estructuras se empaquetarán utilizando las técnicas proporcionadas por cualquier compilador que el OP esté utilizando. –

+0

@John, en esta muestra, la alineación se indica imprimiendo su valor. Pasarías el entero a 'posix_memalign' o algo así. @Anteru ya habrá descubierto una forma de hacerlo, sospecho. –

+1

No veo cómo se obtiene la alineación aquí. Es posible que en realidad no estés implementando la alineación aquí (solo muestra cómo puedes usar plantillas para indicar un posible tamaño de alineación), pero en ese caso, ¿no obtendrías lo mismo con una clase de plantilla normal (sin heredar de otra clase con plantilla), donde Align16 hereda de DeAllocator <16> y Align8 hereda de DeAllocator <8> (y no hereda múltiples)? – Patrick

7

mixins son el enfoque correcto, sin embargo, la sobrecarga del operador nuevo no lo es. Esto logrará lo que necesita:

__declspec(align(256)) struct cachealign{}; 
__declspec(align(4096)) struct pagealign{}; 
struct DefaultAlign{}; 
struct CacheAlign:private cachealign{}; 
struct PageAlign: CacheAlign,private pagealign{}; 

void foo(){ 
DefaultAlign d; 
CacheAlign c; 
PageAlign p; 
std::cout<<"Alignment of d "<<__alignof(d)<<std::endl; 
std::cout<<"Alignment of c "<<__alignof(c)<<std::endl; 
std::cout<<"Alignment of p "<<__alignof(p)<<std::endl; 
} 

imprime

Alignment of d 1 
Alignment of c 256 
Alignment of p 4096 

para GCC, utilice

struct cachealign{}__attribute__ ((aligned (256))); 

en cuenta que hay una selección automática de la alineación más grande, y esto funciona para los objetos colocados en la pila, los que están nuevos y como miembros de otras clases. Tampoco agrega ningún virtual y asumiendo EBCO, no hay un tamaño extra para la clase (fuera del relleno necesario para la alineación).

1

con Visual Studio Express 2010, no parece el ejemplo anterior para trabajar con el nuevo:

CacheAlign *c = new CacheAlign; 

se __align_of dar (c) == 4 (es de esperar que supongo), pero la dirección de una El primer miembro de CacheAlign no está alineado según lo solicitado.

No es una pregunta reciente, pero si entiendo el OP correctamente, tiene clases que son hijos de una clase principal que define el asignador & deallocator, y todos pueden requerir una alineación específica. ¿Qué hay de malo en tener un nuevo simple que llama a un asignador privado que hace el trabajo real y recibe un argumento de alineación, una versión genérica con alineación predeterminada en la clase padre heredada o sobrecargada con una versión que especifica la alineación correcta?