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 *)?

Author: Stephen Lin, 2013-03-08

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 to T2", jeśli wyrażenie typu "pointer to T1" może być jawnie przekonwertowane do typu "pointer to T2" 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ówno T1, jak i T2 są typami układu standardowego (3.9), A wymagania wyrównania T2 nie są bardziej rygorystyczne niż te, które z T1, lub jeśli którykolwiek z typów jest nieważny.

  1. int[N] i int[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]}).

  2. 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", gdzie T jest typem obiektu, może być przekonwertowana na wartość PR typu "pointer to CV void". Wynik konwersji " wskaźnika do CV T" na "wskaźnik do CV void" wskazuje na początek miejsca przechowywania, w którym znajduje się obiekt typu T, tak jakby obiekt był najbardziej pochodnym obiektem (1.8) typu T (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 CV2 T", gdzie T 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.

 21
Author: Joseph Mansfield,
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.

 1
Author: AxelOmega,
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