Przekształcenie tablicy 1-d w tablicę wielowymiarową
Biorąc pod uwagę cały standard C++11, czy jest możliwe, aby jakakolwiek zgodna implementacja udała się z pierwszym twierdzeniem poniżej, ale zawiodła drugie?
#include <cassert>
int main(int, char**)
{
const int I = 5, J = 4, K = 3;
const int N = I * J * K;
int arr1d[N] = {0};
int (&arr3d)[I][J][K] = reinterpret_cast<int (&)[I][J][K]>(arr1d);
assert(static_cast<void*>(arr1d) ==
static_cast<void*>(arr3d)); // is this necessary?
arr3d[3][2][1] = 1;
assert(arr1d[3 * (J * K) + 2 * K + 1] == 1); // UB?
}
Jeśli nie, to jest to technicznie UB, czy nie, i czy ta odpowiedź zmienia się, jeśli pierwsze twierdzenie zostanie usunięte (czy reinterpret_cast
gwarantuje zachowanie adresów tutaj?)? Co zrobić, jeśli zmiana kształtu jest wykonywana w przeciwnym kierunku (3D do 1D) lub z tablicy 6x35 do tablicy 10x21?
EDIT: Jeśli odpowiedź jest że jest to UB z powodu reinterpret_cast
, Czy Jest jakiś inny ściśle zgodny sposób przekształcania (np. poprzez static_cast
do / z pośredniego void *
)?
2 answers
reinterpret_cast
of references
Norma mówi, że lvalue typu T1
może być {[3] } do odniesienia do T2
, Jeśli wskaźnik do T1
może być {[3] } do wskaźnika do T2
(§5.2.10/11):
Wyrażenie lvalue typu
T1
może być oddane do typu "reference toT2
", jeśli wyrażenie typu "pointer toT1
" może być jawnie przekonwertowane do typu "pointer toT2
" przy użyciu reinterpret_cast.
Więc musimy ustalić, czy int(*)[N]
Może być konwertowane na int(*)[I][J][K]
.
reinterpret_cast
of pointers
Wskaźnik do T1
może być reinterpret_cast
do wskaźnika do T2
, Jeśli zarówno T1
, jak i T2
są typami standardowego układu i T2
nie ma bardziej rygorystycznych wymagań wyrównania niż T1
(§5.2.10/7):
Gdy wartość prvalue v typu "wskaźnik do T1" jest konwertowana na typ "wskaźnik do cv T2", wynikiem jest
static_cast<cv T2*>(static_cast<cv void*>(v))
, Jeśli zarównoT1
, jak iT2
są typami układu standardowego (3.9), A wymagania wyrównaniaT2
nie są bardziej rygorystyczne niż te, które zT1
, lub jeśli którykolwiek z typów jest nieważny.
-
Są
int[N]
iint[I][J][K]
typami układów standardowych?int
jest typem skalarnym, a tablice typów skalarnych są uważane za typy standard-layout (§3.9/9).Typy skalarne, typy klas standard-layout (Klauzula 9), tablice takich typów i wersje tych typów kwalifikowane cv (3.9.3) są zbiorczo nazywane typami standard-layout ([89]}typy standard-layout ([90]}).
-
Robi
int[I][J][K]
nie mają bardziej rygorystycznych wymagań dotyczących dostosowania niżint[N]
.Wynik operatora
alignof
daje wymóg wyrównania typu obiektu kompletnego (§3.11/2).Wynik operatora
alignof
odzwierciedla wymóg wyrównania typu w przypadku obiektu kompletnego.Ponieważ dwie tablice nie są podobiektami żadnego innego obiektu, są obiektami kompletnymi. Zastosowanie
alignof
do tablicy daje Wymaganie wyrównania typu elementu (§5.3.6/3):Gdy
alignof
zostanie zastosowana do typu tablicy, wynikiem będzie wyrównanie typu elementu.Więc oba typy tablic mają takie same wymagania wyrównania.
To sprawia, że reinterpret_cast
jest ważne i równoważne:
int (&arr3d)[I][J][K] = *reinterpret_cast<int (*)[I][J][K]>(&arr1d);
Gdzie *
i &
są operatorami wbudowanymi, co jest wtedy równoważne:
int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));
static_cast
przez void*
static_cast
do {[42] } jest dozwolone przez standardowe konwersje (§4.10/2):
Wartość PR typu" pointer to CV
T
", gdzieT
jest typem obiektu, może być przekonwertowana na wartość PR typu "pointer to CV void". Wynik konwersji " wskaźnika do CVT
" na "wskaźnik do CV void" wskazuje na początek miejsca przechowywania, w którym znajduje się obiekt typuT
, tak jakby obiekt był najbardziej pochodnym obiektem (1.8) typuT
(to znaczy nie jest podobiektem klasy bazowej).
static_cast
do int(*)[I][J][K]
jest wtedy dozwolone (§5.2.9/13):
Wartość PR typu "pointer to CV1
void
" może być przekonwertowana na wartość PR typu "pointer to CV2T
", gdzieT
jest typem obiektu, a cv2 jest tą samą kwalifikacją cv, lub większą kwalifikacją cv niż cv1.
Więc obsada jest w porządku! ale czy możemy uzyskać dostęp do obiektów poprzez nowe odniesienie do tablicy?
Dostęp do elementów tablicy
Wykonywanie zapisu tablicy na tablicy takiej jak {[55] } jest równoważne *((E1)+(E2))
(§5.2.1/1). Rozważmy następujący zapis tablicy:
arr3d[3][2][1]
Po pierwsze, arr3d[3]
jest równoważne *((arr3d)+(3))
. Lvalue arr3d
przechodzi konwersję tablicy na wskaźnik, aby dać int(*)[2][1]
. Nie ma wymogu, aby tablica bazowa była odpowiedniego typu, aby wykonać tę konwersję. Następnie uzyskuje się dostęp do wartości wskaźników (co jest w porządku w §3.10), a następnie dodaje się do niej wartość 3. Ta arytmetyka wskaźnika jest również dobra (§5.7/5):
Jeśli zarówno operand wskaźnika a wynik wskazuje na elementy tego samego obiektu array lub jeden obok ostatniego elementu obiektu array, ewaluacja nie spowoduje przepełnienia; w przeciwnym razie zachowanie jest niezdefiniowane.
Ten wskaźnik jest dereferenced dać int[2][1]
. Proces ten przebiega w ten sam sposób dla dwóch kolejnych indeksów dolnych, co daje ostateczny int
lvalue przy odpowiednim indeksie tablicy. Jest to lvalue ze względu na wynik *
(§5.3.1/1):
Operator unary * wykonuje indirection: wyrażenie, do którego jest stosowane, jest wskaźnikiem do typu obiektu lub wskaźnikiem do typu funkcji, a wynikiem jest lvalue odnoszące się do obiektu lub funkcji, na które wskazuje wyrażenie.
Jest wtedy w porządku, aby uzyskać dostęp do rzeczywistego obiektu int
przez ten lvalue, ponieważ lvalue jest również typu int
(§3.10/10):
Jeśli program próbuje uzyskać dostęp do przechowywanej wartości obiektu za pomocą wartości glvalue innej niż jeden z następujących typów zachowania jest niezdefiniowany:
- Typ dynamiczny obiektu
- [...]
Więc chyba, że coś przeoczyłem. Powiedziałbym, że ten program jest dobrze zdefiniowany.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-08 00:04:58
Mam wrażenie, że to zadziała. Przydzielasz ten sam kawałek sąsiedniej pamięci. Wiem, że standard C gwarantuje, że będzie przylegał do siebie. Nie wiem co jest napisane w standardzie C++11.
Jednak pierwsze twierdzenie powinno być zawsze prawdziwe. Adres pierwszego elementu tablicy będzie zawsze taki sam. Wszystkie adresy pamięci będą takie same, ponieważ ten sam kawałek pamięci jest alokowany.
Chciałbym zatem również powiedzieć, że drugie twierdzenie będzie zawsze bądź wierny. Przynajmniej tak długo, jak kolejność elementów jest zawsze w rzędzie głównym porządku. Jest to również gwarantowane przez standard C i byłbym zaskoczony, gdyby standard C++11 mówił coś inaczej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-07 23:11:36