2011-01-03 14 views
7

Czy poniższe legalne C++ ma dobrze zdefiniowane zachowanie?tablica znaków jako pamięć dla miejsca docelowego nowa

class my_class { ... }; 

int main() 
{ 
    char storage[sizeof(my_class)]; 
    new ((void *)storage) my_class(); 
} 

Czy jest to problematyczne z powodu kwestii rzutowania/wyrównania wskaźnika?

+0

Dla mnie wszystko jest w porządku. –

+2

Nie, elipsy nie są legalne w tym kontekście ... (Podpowiedź: jeśli chcesz zapytać, czy kod jest dobrze zdefiniowany, najpierw musisz go skompilować.) – GManNickG

+0

Daj mi proszę powiedzieć, jakie jest użycie robienia powyższej rzeczy w prawdziwym świecie pogramowania. – vrbilgi

Odpowiedz

12

Tak, to problematyczne. Po prostu nie masz gwarancji, że pamięć jest prawidłowo wyrównana.

Podczas gdy istnieją różne sztuczki, aby uzyskać pamięć z odpowiednim wyrównaniem, najlepiej jest użyć Boost's lub C++ 0x aligned_storage, które ukrywają te sztuczki od ciebie.

Potem wystarczy:

// C++0x 
typedef std::aligned_storage<sizeof(my_class), 
           alignof(my_class)>::type storage_type; 

// Boost 
typedef boost::aligned_storage<sizeof(my_class), 
         boost::alignment_of<my_class>::value>::type storage_type; 

storage_type storage; // properly aligned 
new (&storage) my_class(); // okay 

pamiętać, że w C++ 0x, za pomocą atrybutów, można po prostu to zrobić:

char storage [[align(my_class)]] [sizeof(my_class)]; 
+0

Dzięki, jest to bardzo przydatne wiedzieć. – bluescarni

+0

Niestety, jeszcze nie całkiem :) O ile mi wiadomo, typem pamięci jest wbudowany typ integer. Jak tego używać? Czy mogę swobodnie rzutować na typ my_class za pomocą void *? – bluescarni

+0

Przepraszam, chciałem powiedzieć: "rzucaj swobodnie w tę iz powrotem od/do wskaźników do instancji my_class" ... Lub coś w tym stylu :) – bluescarni

2

Jest co najmniej problematyczna ze względu na wyrównanie.

W większości architektury nie-Intel kod wygeneruje "błąd magistrali" z powodu złego wyrównania lub będzie bardzo powolny z powodu pułapek procesora potrzebnych do unieruchomienia dostępu do pamięci.

W przypadku architektury Intel zwykle będzie to nieco wolniej niż zwykle. Poza przypadkami, w których zaangażowane są niektóre operacje SSE, może również ulec awarii.

0

Tablica char może nie być prawidłowo dopasowana do rozmiaru myclass. W niektórych architekturach oznacza to wolniejszy dostęp, a na innych oznacza awarię. Zamiast char, należy użyć typu, którego wyrównanie jest równe lub większe niż w przypadku struct, które jest podawane przez największe wymagania wyrównania któregokolwiek z jego elementów.

#include <stdint.h> 

class my_class { int x; }; 

int main() { 
    uint32_t storage[size]; 
    new(storage) my_class(); 
} 

Aby przydzielić wystarczającej ilości pamięci do jednego my_class przykład, myślę size powinno być sizeof(my_class)/sizeof(T), gdzie T jest dowolny typ użyć, aby uzyskać prawidłowe wyrównanie.

+0

"... którego wyrównanie jest równe lub większe niż w strukturze, która jest nadawana przez największe wymagania wyrównania któregokolwiek z jej członków" Nie sądzę, że jest to zagwarantowane, wyrównanie jest całkowicie zdefiniowane w ramach implementacji. (To znaczy, nie jest gwarantowane, że wyrównanie musi być wyrównaniem najostrzejszego członka.) Ponadto 'int' może być większe niż 32-bity. – GManNickG

+0

@GMan: Masz rację. W większości przypadków działa. Tutaj masz link: http://stackoverflow.com/questions/364483/determining-the-alignment-of-c-c-curures-in-relation-to-its-members –

3

Jak ludzie mają tu wspomnieć, to nie będzie koniecznie działają ze względu na ograniczenia związane z dopasowaniem. Istnieje kilka sposobów na prawidłowe wyrównanie. Po pierwsze, jeśli masz kompilator zgodny ze standardem C++ 0x, możesz użyć operatora alignof, aby spróbować wymusić prawidłowe wyrównanie. Po drugie, możesz dynamicznie alokować tablicę znaków, ponieważ pamięć od operatora new ma być wyrównana w taki sposób, że cokolwiek może jej użyć poprawnie. Po trzecie, możesz spróbować zapisać tablicę znaków w unii z typem, który ma maksymalne możliwe wyrównanie w twoim systemie; Wierzę, że this article ma pewne informacje na ten temat (choć jest przeznaczony dla C++ 03 i na pewno nie jest tak dobry, jak operator alignof, który wkrótce wychodzi).

Mam nadzieję, że to pomoże!

2

Jeśli ktoś chce uniknąć Boost lub C++ 1x, ten kompletny kod działa zarówno w GCC, jak i MSVC. Kod specyficzny dla MSVC oparty jest na Chromium aligned_memory.h. Jest trochę bardziej skomplikowany niż wersja GCC, ponieważ MSVC __declspec(align(.)) akceptuje tylko wartości wyrównania literowego, a to jest przetwarzane przy użyciu specjalizacji szablonu dla wszystkich możliwych dopasowań.

#ifdef _MSC_VER 

template <size_t Size, size_t Align> 
struct AlignedMemory; 

#define DECLARE_ONE_ALIGNED_MEMORY(alignment) \ 
template <size_t Size> \ 
struct __declspec(align(alignment)) AlignedMemory<Size, alignment> { \ 
    char mem[Size]; \ 
}; 

DECLARE_ONE_ALIGNED_MEMORY(1) 
DECLARE_ONE_ALIGNED_MEMORY(2) 
DECLARE_ONE_ALIGNED_MEMORY(4) 
DECLARE_ONE_ALIGNED_MEMORY(8) 
DECLARE_ONE_ALIGNED_MEMORY(16) 
DECLARE_ONE_ALIGNED_MEMORY(32) 
DECLARE_ONE_ALIGNED_MEMORY(64) 
DECLARE_ONE_ALIGNED_MEMORY(128) 
DECLARE_ONE_ALIGNED_MEMORY(256) 
DECLARE_ONE_ALIGNED_MEMORY(512) 
DECLARE_ONE_ALIGNED_MEMORY(1024) 
DECLARE_ONE_ALIGNED_MEMORY(2048) 
DECLARE_ONE_ALIGNED_MEMORY(4096) 

#else 

template <size_t Size, size_t Align> 
struct AlignedMemory { 
    char mem[Size]; 
} __attribute__((aligned(Align))); 

#endif 

template <class T> 
struct AlignedMemoryFor : public AlignedMemory<sizeof(T), __alignof(T)> {}; 
Powiązane problemy