2010-03-02 19 views
31

Sto sovraccaricando operator new, ma di recente ho riscontrato un problema con l'allineamento. Fondamentalmente, ho una classe IBase che fornisce operator new e delete in tutte le varianti richieste. Tutte le classi derivano da IBase e quindi utilizzano anche gli allocatori personalizzati.operatore nuovo sovraccarico e allineamento

Il problema che sto affrontando ora è che ho un figlio Foo che deve essere allineato a 16 byte, mentre tutti gli altri vanno bene quando sono allineati a 8 byte. Il mio allocatore di memoria tuttavia si allinea ai limiti di 8 byte solo per impostazione predefinita, quindi ora il codice in IBase::operator new restituisce un pezzo di memoria inutilizzabile. Come si dovrebbe risolvere questo problema correttamente?

Posso semplicemente forzare tutte le allocazioni a 16 byte, che funzioneranno correttamente finché non verrà visualizzato un tipo allineato a 32 byte. Capire l'allineamento all'interno di operator new non sembra essere banale (posso fare una chiamata di funzione virtuale per ottenere l'allineamento effettivo?) Qual è il modo consigliato di gestirlo?

So che malloc si suppone che restituisca un pezzo di memoria che è adeguatamente allineato per tutto, purtroppo, questo "tutto" non include i tipi di SSE e mi piacerebbe davvero farlo funzionare senza richiedere all'utente di ricordare quale tipo ha quale allineamento.

+0

Forse potresti utilizzare un IBase diverso (IBase16?) Per i pochi oggetti che hanno requisiti di allineamento speciali. –

+0

Si potrebbe anche creare questa classe base (IBase16, IBase32) con template, quindi è possibile utilizzare IBase . – Patrick

+0

È possibile eseguire l'allineamento sui limiti a 64 byte. :) – Bill

risposta

20

Questa è una possibile soluzione. Sarà sempre scegliere l'operatore con il maggior allineamento in una determinata gerarchia:

#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 
} 

Si basa sulla regola predominio: Se c'è un'ambiguità nella ricerca, e l'ambiguità è tra i nomi di un derivato e una base virtuale classe, viene invece preso il nome della classe derivata.


Le domande sono state risorto perché DeAllocator<I> eredita DeAllocator<I/2>. La risposta è perché in una determinata gerarchia potrebbero esserci diversi requisiti di allineamento imposti dalle classi. Immaginare che IBase non ha requisiti di allineamento, A ha requisito 8 byte e B ha 16 requisito byte ed eredita A:

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

Alignment<16> e Alignment<8> sia esporre un operator new. Se ora si dice new B, il compilatore cercherà operator new in B e troverà due funzioni:

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

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

Così, questo sarebbe ambiguo e noi non riuscirebbe a compilare: Nessuno di questi nascondono l'altra uno. Ma se ora eredita Alignment<16> praticamente da Alignment<8> e fare A e B loro ereditare virtualmente, il operator new in Alignment<8> saranno nascoste:

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

Questa regola speciale nascondiglio (chiamato anche dominanza regola) ma funziona solo se tutti gli oggettiAlignment<8> sono gli stessi. Quindi ereditiamo sempre virtualmente: in questo caso, c'è solo unAlignment<8> (o 16, ...) oggetto esistente in una data gerarchia di classi.

+0

Bel uso di modelli e mi piace il modo in cui si pensa, ma mi deve mancare qualcosa di ovvio visto che non vedo come viene eseguito l'allineamento. Si presume che le strutture vengano impacchettate usando le tecniche fornite da qualsiasi compilatore che l'OP sta usando? –

+0

@John, in questo esempio, l'allineamento viene indicato stampando il suo valore. Passerai il numero intero a 'posix_memalign' o qualcosa del genere. @Anteru avrà già trovato un modo per farlo, sospetto. –

+1

Non vedo come si ottiene l'allineamento qui. Forse non stai davvero implementando l'allineamento qui (solo mostrando come potresti usare i modelli per indicare una possibile dimensione di allineamento), ma in tal caso, non otterresti lo stesso con una normale classe basata su modelli (non ereditando da un'altra classe basata su modelli), dove Align16 eredita da DeAllocator <16> e Align8 eredita da DeAllocator <8> (e nessuna ereditarietà multipla)? – Patrick

7

mixin sono l'approccio giusto, tuttavia l'operatore di overloading non lo è. Questo porterà a compimento quello che vi serve:

__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; 
} 

Stampe

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

per GCC, usare

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

Si noti che non v'è la selezione automatica dei più grandi di allineamento, e questo funziona per oggetti posti nello stack, quelli che sono new'd e come membri di altre classi. Né aggiunge alcun virtuale e presuppone EBCO, nessuna dimensione extra per la classe (al di fuori del padding necessario per l'allineamento stesso).

1

Utilizzando Visual Studio Express 2010, l'esempio di cui sopra non sembra funzionare con il nuovo:

CacheAlign *c = new CacheAlign; 

darà __align_of (c) == 4 (da aspettarsi suppongo), ma l'indirizzo di un il primo membro di CacheAlign non è allineato come richiesto.

Non una domanda recente, ma se ho capito bene l'OP, ha classi che sono figli di una classe genitore che definiscono l'allocatore & deallocator e possono richiedere tutti un allineamento specifico. Cosa c'è di sbagliato nell'avere un nuovo semplice che chiama un allocatore privato che esegue il lavoro effettivo e riceve un argomento di allineamento: una versione generica con allineamento predefinito nella classe padre ereditata o sovraccarica con una versione che specifica l'allineamento corretto?

Problemi correlati