wskaźniki a tablice wielowymiarowe

0

Witam mam taki problem zrobiłem program (dopiero się uczę) który do tablicy wielowymiarowej wpisuje wartości a później chce zobaczyć wartość z jakiegoś położenia(Inny użytkownik mi pomógł) i normalnie przez wpisanie np. tab[2][2] działa wypisuje poprawna wartość. Jak robię to na wskaźnikach nie chce działać.

#include <iostream>
#include <stdlib.h>

using namespace std;

int main()
{
    int index = 3;
    int index1 = 1;
    int number = 0;
    int i,j;
    int tab[index][index1];
    int *wsk;

    wsk = &tab[i][j];

    for ( i = 0  ; i < index ; i++)
    {
        for ( j = 0 ; j < index1 ; j++ )
        {
            cout << "podaj liczbe: ";
            cin >> number;

            tab[i][j] = number;
            cout << "tab[" << i << "][" << j << "] = " << number << endl;
            system("clear");
        }
    }
    //cout << tab[2][0] << endl;
    cout << "podaj 'i'' i 'j'" << endl;
    cin >> i;
    cin >> j;

    cout << *wsk[i][j] << endl;

    return 0;
}
 
0

Źle to robisz, przeczytaj jakiś kurs?

1

Wskaźnik int* można indeksować tak jakby był tablicą, ale jednowymiarową.

Jeżeli masz tablicę

int tab[2][2] = {{1,2},{3,4}};

to pod wskaźnikiem

int *wsk = &tab[0][0];

możesz zobaczyć cztery inty dwuwymiarowej tablicy, które kolejno ułożone są w pamięci: wsk[0]==1, wsk[1]==2, wsk[2]==3, wsk[3]==4.

Ale nie zadziała to jak dwuwymiarowa tablica.

4
Azarien napisał(a):

Wskaźnik int* można indeksować tak jakby był tablicą, ale jednowymiarową.

Jeżeli masz tablicę

int tab[2][2] = {{1,2},{3,4}};

to pod wskaźnikiem

int *wsk = &tab[0][0];

możesz zobaczyć cztery inty dwuwymiarowej tablicy, które kolejno ułożone są w pamięci: wsk[0]==1, wsk[1]==2, wsk[2]==3, wsk[3]==4.

Ale nie zadziała to jak dwuwymiarowa tablica.

Absolutnie nie! To jest undefined behavior (5.7/5 w C++14, 6.5.6/8 w C11, przekroczenie rozmiaru tablicy przy dodawniu wskaźnik + offset).

A co do tematu - chcesz mieć tablice wielowymiarowe? Użyj normalnej tablicy jednowymiarowej i odpowiednio zaimplementuj dostępy do niej, tak aby "udawała" tablicę wielowymiarową. Każda inna droga to droga pod górę i, mimo że usilnie uczy się jej wszędzie, prowadzi donikąd.

1

Wiedziałem że zaraz ktoś się przyczepi że UB, ale nie ma racji.

Nie wiem co to jest "5.7/5", w C++14 punkt 5.7 to “Additive operators”, a w C11 nie ma takiego punktu.
A stare standardy są stare.

#include <stdio.h>

int main()
{
  int tab[2][2] = {{1,2},{3,4}};
  int *wsk = &tab[0][0];

  printf("%d %d %d %d\n", wsk[0], wsk[1], wsk[2], wsk[3]);
}

Nie ma przekroczenia tablicy. Tablica ma cztery elementy typu int, i jest gwarantowana ich ciągłość w pamięci.
Wskaźnik nie odnosi się do elementów poza tablicą. Ponieważ elementy tablicy są typu int, a wskaźnik jest typu int*, jego dereferencja nie łamie zasad “strict aliasing”.

6
Azarien napisał(a):

Wiedziałem że zaraz ktoś się przyczepi że UB, ale nie ma racji.

Nie wiem co to jest "5.7/5", w C++14 punkt 5.7 to “Additive operators”, a w C11 nie ma takiego punktu.
A stare standardy są stare.
...

Nie ma przekroczenia tablicy. Tablica ma cztery elementy typu int, i jest gwarantowana ich ciągłość w pamięci.
Wskaźnik nie odnosi się do elementów poza tablicą. Ponieważ elementy tablicy są typu int, a wskaźnik jest typu int*, jego dereferencja nie łamie zasad “strict aliasing”.

Na takie farmazony mam tylko jedną odpowiedź:

1

Nie mogę namierzyć źródła, ale pamiętam wypowiedź experta, że w takich sytuacjach bezpieczne jest użyć "reinterpret_cast<>"

Znalazłem. Tablica dwuwymiarowa

for( std::size_t i = 0; i < sizeof( tab ) / sizeof( int ); ++i )
         std::cout << * reinterpret_cast < const int *>( reinterpret_cast < const char *>( & tab ) + i * sizeof( int ) ) << ' '; 
1
Endrju napisał(a):

Na takie farmazony mam tylko jedną odpowiedź:

Miałem nadzieję że rzucisz jakimś akapitem ze standardu. Ale widzę że nie ma kontrargumentu i zaczęło się obrzucanie błotem.

Tablica int[2][2] ma gwarantowaną ciągłość elementów. Wyjechania poza zaalokowaną tablicę również nie ma.

Ponadto int* to int*, nie pamięta na jakich rozmiarów i o ilu wymiarach tablicy element wskazuje. Jeżeli wsk = &tab[0][0], to wsk+3 = &tab[1][1].

Element tablicy był intem, dereferencja następuje do inta, nie ma złamania zasady strict aliasing.

3

@Azarien: 5.7/5: If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

1

@Azarien: Element *(a+3) dla a[2][2] nie jest elementem spoza tablicy a? a jest niczym innym jak tablicą tablic. Czyli tablicą. Dwuelementową. Której elementy są tablicami. A Ty odwołujesz się do trzeciego elementu tablicy dwuelementowej.

1

Wskaźnik to nie tablica.
Wskaźnik to wskaźnik. Może wskazywać na element wewnątrz tablicy - i stosując arytmetykę wskaźników, można poruszać się w ramach tejże tablicy.

Taki kod jest prawidłowy:

int tab[10] = {0,1,2,3,4,5,6,7,8,9};
int *p = &tab[5];
int a = p[4]; // tab[9]
int b = p[-5]; // tab[0]

tak długo, jak wskaźnik - po przeliczeniu adresów - wskazuje na istniejący element tablicy, można się odwoływać do wskazywanych elementów.
Wskaźnik sam w sobie nie powoduje że jakiś element istnieje albo nie, tylko tablica.

Twierdzę również, że to samo zachodzi w przypadku tablicy wielowymiarowej.

int tab[2][2] = {{1,2},{3,4}};
int *p = &tab[0][0];
int a = p[3]; // tab[1][1]

Nie ma tu nigdzie wyjechania poza tablicę. Tablicą jest tab[2][2], jest ona czteroelementowa, a ja odwołuję się do ostatniego, czwartego elementu. Nie ma przekroczenia żadnego zakresu.

Nie macie, koledzy, racji.

0
Endrju napisał(a):
Azarien napisał(a):

Wskaźnik int* można indeksować tak jakby był tablicą, ale jednowymiarową.

Jeżeli masz tablicę

int tab[2][2] = {{1,2},{3,4}};

to pod wskaźnikiem

int *wsk = &tab[0][0];

możesz zobaczyć cztery inty dwuwymiarowej tablicy, które kolejno ułożone są w pamięci: wsk[0]==1, wsk[1]==2, wsk[2]==3, wsk[3]==4.

Ale nie zadziała to jak dwuwymiarowa tablica.

Absolutnie nie! To jest undefined behavior (5.7/5 w C++14, 6.5.6/8 w C11, przekroczenie rozmiaru tablicy przy dodawniu wskaźnik + offset).

Pewnie z c# i java to wydumałeś.
W c nie ma takich zboczeń... żeby alkować proste tablice jakoś liniami, czy coś tam.

1
pingwindyktator napisał(a):

@Azarien: Element *(a+3) dla a[2][2] nie jest elementem spoza tablicy a? a jest niczym innym jak tablicą tablic. Czyli tablicą. Dwuelementową. Której elementy są tablicami.
Tak, dwuelementowymi. Razem tablica zajmuje tyle pamięci co cztery inty.

A Ty odwołujesz się do trzeciego elementu tablicy dwuelementowej.

Nie.
Element *(a+3) a nawet *(a+2) dla int a[2][2] rzeczywiście wychodzi poza tablicę, gdyż odwołuje się do nieistniejącego wiersza.
Ale zauważ, że każde +1 przeskakuje o cały wiersz, czyli o 2 inty.

Ja do indeksacji używam wskaźnika typu int*. Każde +1 przeskakuje tylko o jednego inta. I ptr[3] odwołuje się do czwartego inta, drugiego w drugim wierszu, a nie do czwartej dwuelementowej tablicy.

1

@Azarien:

int a[2][2];
cout << sizeof(a) / sizeof(*a);
1

@Azarien: No to, że tablica jest dwuelementowa. A Ty odwołujesz się do elementu za ową tablicą. Czymkolwiek ta tablica nie jest - to jest UB. Zgadza się, że standard gwarantuje ciągłość pamięci, ale jak masz wskaźnik do tablicy "wewnętrznej" i robisz te swoje cuda, to ową tablice opuszczasz. I tu masz UB. Innymi słowi: standard gwarantuje, że owe dwa obiekty (tablica a[0] i tablica a[1]) będą w pamięci obok siebie, ale nie możesz tak biegać sobie wskaźnikiem gdzie chcesz. Przykład:

int a[5];
a[6]; // UB
auto b = a + 6; // UB
auto c = b - 6;
c != a;
1

Innymi słowi: standard gwarantuje, że owe dwa obiekty (tablica a[0] i tablica a[1]) będą w pamięci obok siebie, ale nie możesz tak biegać sobie wskaźnikiem gdzie chcesz.

Mogę, bo mogę stworzyć osobny wskaźnik na każdy element tablicy. Ciągłość pamięci gwarantuje, że p+3 jest równe &tab[1][1], a zgodność dereferowanego typu (int) gwarantuje że zasada strict aliasing nie jest łamana.

#include <iostream>
using namespace std;

int main()
{
	int a[2][2];

	int *p = &a[0][0];

	cout << (&a[1][1] == &p[3]) << endl;
}

Gdyby to nie było dozwolone, to wymóg ciągłości elementów w tablicy byłby bez sensu, bo nie można byłoby z tego skorzystać.

0

W łeb Ci wale paragrafami a Ty dalej swoje. Jak dla mnie EOT.

0

Twoje paragrafy nie mają zastosowania. Jestem przerażony poziomem wiedzy na temat wskaźników i tablic u osób, które niby potrafią czytać „paragrafy”.

1
pingwindyktator napisał(a):

W łeb Ci wale paragrafami a Ty dalej swoje. Jak dla mnie EOT.

Bzdury wygadujesz i tyle.

int a[5][5][5];

5x5x5 = 125, zatem to jest tablica 125-elementowa

int *p = a;

p[55] = 7;

// to samo co:
a[2][1][0] = 7; // 2x25 + 5 x 1 + 0 = 55

1

Nie chcę być tu stroną, ale na razie wszędzie znajduję tylko informacje że @Azarien ma rację.

http://www.fredosaurus.com/notes-cpp/arrayptr/23two-dim-array-memory-layout.html
http://stackoverflow.com/a/2565310
And last but not least: "Programming: Principles and Practice Using C++", Bjarne Stroustrup, 24.3 (końcówka)

Edit: gwóźdź do trumny UB:

The C++ Programming Language (wiadomo kogo), 7.4.3 Passing Arrays

0

Nikt się nie kłóci o to, w jaki sposób to działa, bo dokładnie w taki jak piszecie. Ale to bez znaczenia jeśli rozmawiamy o tym czy jest to UB czy nie.

13

Cześć,

Dostałem linka do tego wątku, i po jego lekturze stwierdziłem, że przesiedzenie 2h nad standardem w ramach zabicia nudy w pociągu brzmi kusząco (na szczęście w shinkansenie pomiędzy Kyoto a Tokyo jest net przez całą drogę).

TL;DR: wg moich ustaleń @Endrju ma rację wg standardu i to jest UB (nie patrzyłem na to, czy popularne implementacje to dodefiniowują, co mogą robić btw; skupiłem się głównie na C++14).

Troche notatek z tego na co patrzyłem:

Zacząłem od przeczytania tego wątku ze trzy razy, plus fragmentu standardu C++14 i C11, który @Endrju podlinkował. W tym ostatnim najciekawsze jest to zdanie (zresztą już cytowane przez @pingwindyktator ):

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

Tu pojawiły się dwa pytania - co to jest "array" i co to jest "object", i jak to się ma do dwu-/wielowymiarowych tablic.

"Object" jest wyjasniany 1.8; ciekawe są dwa fragmenty:

C++14 1.8/2 [intro.object]
Objects can contain other objects, called subobjects. A subobject can be a member subobject (9.2), a base class subobject (Clause 10), or an array element. An object that is not a subobject of any other object is called a complete object

C++14 1.8/3 [intro.object]
For every object x, there is some object called the complete object of x, determined as follows:
— If x is a complete object, then x is the complete object of x.
— Otherwise, the complete object of x is the complete object of the (unique) object that contains x

Przenosząc powyższe na przykład tablicy dwu-wymiarowej int a[2][3] można powiedzieć, że:

  • "a" jest "kompletnym obiektem" (sry za słabe tłumaczenie; na potrzeby dyskusji wystarczy), który zawiera N "podobiektów" (konkretniej dwa, ale o tym czemu dwa to za chwilę);
  • oba "podobiekty" zawierają po trzy swoje "podobiekty" typu int

Czyli wg. tego można by powiedzieć zarówno, że jest jakaś hierarchia obiektów, jak i że a[0][0] jest elementem tego samego "kompletnego obiektu" bo a[1][1]. Należy zaznaczyć, że to jednak nie oznacza, że są elementem tej samej tablicy ("array") - nie można pociągnąć implikacji w tą stronę.

"Array" jest wyjaśniony natomiast w 8.3.4; ciekawe (wg mnie) są następujące fragmenty:

C++14 8.3.4/3 [dcl.array]
When several “array of” specifications are adjacent, a multidimensional array is created. [...]

Tutaj standard wprowadza pojęcie "multidimensional array". Dalej:

C++14 8.3.4/7 [dcl.array]
[ Note: Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). [...]

Powyższy cytat jest istotny aby wykazać powiązanie tego na co @Endrju się powołał z samymi arrayami.

Idąc dalej, bardzo ciekawy jest następujący przykład:

C++14 8.3.4/5 [dcl.array]
[ Example: [...]
static int x3d[3][5][7];

declares a static three-dimensional array of integers, with rank 3×5×7. In complete detail, x3d is an array of three items; each item is an array of five arrays; each of the latter arrays is an array of seven integers.

A więc explicit jest napisane, że jest to tablica tablic tablic. Nie jest natomiast nigdzie w okolicy napisane, że tablica tablic (aka tablica wielowymiarowa) jest:

  1. kompatybilna z tablicą jednowymiarową o tej samej wielkości (w rozumieniu storage space)
  2. rzutowalna pomiędzy takimi tablicami
  3. pointery na takie tablice są kompatybilne (tj. można zgodnie ze standardem przerzutować między nimi; można to zapisać w języku oczywiście, jak wiele innych UB)
  4. Nie jest również napisane, że istnieje coś takiego jak "complete array" (analogicznie do "complete object") który miałby "sub-arraye" (analogiczne do "sub-objects").

Jednocześnie znalazłem dwie inne informacje, że gdyby coś takiego było, to by miało prawo działać:

  1. ułożenie elementów w tablicy, jeden po drugim, jest gwarantowane przez standard ("An object of array type contains a contiguously allocated non-empty set of N subobjects of type T" C++14 8.3.4)
  2. żadne z "sub-arrayów" (to mój termin, nie standardu) nie może mieć innego alignmentu niż domyślny (to wynika z zasad związanych z sizeof, na co podpowiedź znalazłem na stackoverflow i o czym faktycznie pisze w standardzie (5.3.3/2 - "When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element." - alignment sub-arraya by sprawił, że sizeof zwróciłby nieprawdziwy wynik, więc tak się stać nie może, bo to by było niezgodne ze standardem)

Mniej więcej w tym momencie byłem przekonany, że słowo "array" w oryginalnym cytacie dotyczy tylko i wyłącznie tego konkretnego arraya, na podstawie którego został stworzony pointer, który sobie po nim biega. Nie dotyczy natomiast "kompletnego obiektu" (ponieważ nie istnieje coś takiego jak "kompletna tablica" w standardzie - jest tylko kompletny obiekt, który jest dużo bardziej ogólnym terminem).

Niemniej jednak jak wiemy, częsty wzorzec jaki się widzi, to przekazywanie tablicy dwu-wymiarowej przez pointer na jej pierwszy element i dwie wielkości (szerokość i wysokość) - ba, jestem pewien, że gdyby moje stare posty odnaleźć, to też o tym pisałem jako o stosowanym rozwiązaniu.
Zacząłem więc sprawdzać, czy jest możliwe przekazanie dwu-wymiarowej tablicy do funkcji nie korzystając z tego tricku (jeśli by nie było możliwe, to może jednak moje przekonanie byłoby błędne).

Oczywiście jak najbardziej jest możliwe - wstawienie nazwy wielowymiarowego arraya jako parametr funkcji wstawia tak naprawdę adres jej pierwszego elementu (array-to-pointer implicit cast się kłania), który jest typu tablica tablic (o jeden wymiar mniejsza), np. (kudos bot, który @kq postawił):

<@Gynvael> cxx: template<typename T> void f(T x) { cout << typeid(x).name(); } int main() { int a[2][3]; f(a); }
< cxx> int (*) [3]

Kolejne pytanie, które się pojawiło, to "czemu u licha używa się rozwiązania, które jest UB, do pracy na tablicach wielowymiarowych?" oraz "czemu nikt się nie skapnął, że to UB?!?!".
Po wrzuceniu kilku zapytań w google wpadł mi fajny link z comp.lang.c FAQ - http://c-faq.com/aryptr/ary2dfunc2.html:

It must be noted, however, that a program which performs multidimensional array subscripting ``by hand'' in this way is not in strict conformance with the ANSI C Standard; according to an official interpretation, the behavior of accessing (&array[0][0])[x] is not defined for x >= NCOLUMNS.

Co jest zgodne z moimi ustaleniami.

Podsumowując to wall-of-text. Owszem, jest to niezgodne ze standarem C++14 i, jeśli wierzyć comp.lang.C FAQ, to przynajmniej z ANSI C również. Teraz trzeba by sprawdzić jeszcze kilka rzeczy:

  1. Czy w C++17 czegoś nie planują zmienić. Stan faktyczny jest taki, że ludzie z tego korzystają, i nie ma chyba żadnych przeciwskazań, żeby przynajmniej dla POD na omawiane zachowanie zezwolić.
  2. Jak reagują na to narzędzia typu typu UBSan (undefined behavior sanitizer)
  3. Poszperać po dokumentacji GCC, LLVM i Visual C++ czy to jakoś oficjalnie dodefiniowują (czy może kiedyś zaczną warningi na to rzucać, albo jak pojawią się w końcu pointery-dowiązane-do-arrayów, zaczną lecieć runtime exceptiony).

Anyway, @kq, rzuć plz na to okiem też - masz dobre oko do standardu, lepsze niż ja.

8

@Gynvael Coldwind o ile pamiętam identyczny wątek skłonił mnie do rejestracji na tym forum
user image

Here it is: http://4programmers.net/Forum/Newbie/145824-problem_z_wypelnieniem_tablicy_dwuwymiarowej?p=959154#id959154

Jak widać stanowisko @Azarien jest od dawna takie samo (tak samo błędne ;) ), więc przekonywanie go nie wygląda specjalnie sensownie, ale i tak spróbuję:

int arr2[10][10];
int arr1[10];

int* ptr2 = &arr2[0][0]; // ptr2 wskazuje na pierwszy element tablicy 10 intów
int* ptr1 = &arr1[0]; // ptr1 wskazuje na pierwszy element tablicy 10 intów

ptr1+11; // UB (tak, samo dodanie w wyniku którego powstanie wskaźnik poza tablicę (za wyjątkiem 1 za) to UB - zapraszam do paragrafów zacytowanych przez @Endrju)
ptr2+11; // jak wyżej. Nie ma znaczenia, że w pamięci jest *inna* tablica zaraz dalej

Faktycznie jednak trzeba przyznać, że standardy (C i C++) gwarantują takie a nie inne rozłożenie tablic wielowymiarowych i że dla uproszczenia¹ mogli by na to po prostu zezwolić - nie jestem w stanie wyobrazić sobie architektury, w której to by było problemem.

Ale twierdzenie, że "u mnie działa, więc musi być dobrze dla generalnego przypadku" jest niedopuszczalne (ahem, fizyka Newtonowska "u mnie działa")

¹ na pewno nie słownictwa w standardach :/

1

To widocznie Stroustrup ma na ten temat inne zdanie niż standard. Fragment "Język C++. Kompendium Wiedzy (2014)"

2

Tak, czytałem standardy (cytaty z C++ wyglądają na copy-paste całych zdań z C) i stwierdzam że jest to luka w standardach, w tym sensie, że nie jest to wprost zezwolone. Nie jest również wprost zabronione.
Tablica zawiera elementy ciągiem - bez żadnych paddingów. Sam wskaźnik nie jest „przywiązany”, nie ma kontroli zakresów. Intencją cytowanego wielokrotnie zdania

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
jest by nie odwoływać się do elementów poza zaalokowanym obiektem. Dlaczego? Gdyż takie odwołanie:

  • może uszkodzić inną zmienną, albo nawet kod (a ciągłość i kolejność w pamięci nawet w przypadku int x,y nie jest gwarantowana)
  • może trafiać w jakiś inny segment czy stronę pamięci, do którego nie mamy praw
  • może być w ogóle nieprawidłowym wskaźnikiem na danej architekturze.
    W omawianym przypadku nie wychodzimy poza całą (wielowymiarową) tablicę. Wskaźnik „nie wie”, czy powstał przez podwójną indeksację tablicy, czy pojedynczą indeksację wskaźnika na tablicę. Implementacja (kompilator) która by „psuła” taką sytuację byłaby zepsuta, bo łamałaby ciągłość elementów tablicy w pamięci.

Jeżeliby traktować powyższy cytat tak dosłownie jak niektórzy tu próbują, nie działałby nawet zwykły malloc:

int *p = malloc(4*sizeof(int));
p[3] = 42;

Przecież wskaźnik nie wskazuje na żaden “array object”, wskazuje jedynie na ciągły obszar pamięci, o którym tylko my wiemy (nie kompilator) że jest prawidłowo zaalokowany. A mimo to nie ma wątpliwości, że taka indeksacja jest prawidłowa.
Podobnie w omawianym przypadku, w którym „źródłem” alokacji pamięci nie jest malloc tylko istniejąca tablica (niezależnie od jej wymiarów), której rozmiar znamy.

Jest jeden przypadek, w którym prawidłowy (pod względem wskazywanej komórki pamięci) wskaźnik nie może być użyty: kiedy łamie zasadę “strict aliasing”:

float f;
int *p = (int*)&f;
*p = 0;

nawet jeżeli sizeof(int)==sizeof(float), i wartości (int)0 oraz (float)0.0 mają identyczną reprezentację bitową, jest to niedozwolone, bo kompilator zakłada, że wskaźnik na inta nigdy nie modyfikuje floata.

W omawianym przypadku wskaźnik na inta modyfikuje inta. Kompilator nie może tego zignorować, zepsuć.

Podsumowując: standardy nie piszą nigdzie jawnie „tak, można tak zrobić”. Ale tym gorzej dla standardów. Implementacja nie może pozwolić sobie na zepsucie takiego odwołania, bo

  • łamałoby to zasadę ciągłości tablicy i pamięci (C++14 1.7.1 The memory available to a C++ program consists of one or more sequences of contiguous bytes. Every byte has a unique address. 1.8.5 An object of trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage. 8.3.4 An object of array type contains a contiguously allocated non-empty set of N subobjects of type T.)
  • łamałoby zasadę strict aliasing opisaną w 3.10.10
2

Implementacja jak najbardziej może sobie pozwolić na złamanie takiego odwołania bo to UB (tylko nie ma powodu, aby to robić, bo żadne optymalizacje na razie z tego nie korzystają, a layout pod spodem faktycznie jest wymuszony).

@ intencje: rozumiem, że takie były prawdopodobnie intencje autorów standardu. Ale to nie intencje się liczą.

Nieprawdą jest, że standard tego nie zakazuje - jest to zakaz nie w prost, i to trochę niefortunny, ale jest.

@Czarny Szczur: każdy może się pomylić.

4

Nie jest również wprost zabronione.

Jest zabronione, patrz wyżej. Jeśli sto reguł na coś "zezwala" i wszystkie kompilatory definiują zachowanie zgodnie z intuicją ale choćby jedna mówi że to to UB, to oficjalnie jest to UB.

Wskaźnik „nie wie”, czy powstał przez podwójną indeksację tablicy, czy pojedynczą indeksację wskaźnika na tablicę. I

to jest argument za UB.

Przykład:

int f(int (&arr) [4]) {
    return arr[6];
}

Taka funkcja nie wie, czy arr jest samotną tablicą (wtedy to oczywisty UB), czy element tablicy wielowymiarowej (kiedy wynik byłby teoretycznie przewidywalny), więc w ogólnym przypadku nie ma innej możliwości niż stwierdzić że to UB.

1

Ja mam dokładnie takie same pojmowanie tego jak @Azarien ponieważ skoro:

C++14 8.3.4/7 [dcl.array]
[ Note: Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). [...]

to:
a[y][x] ==
*(a[y]+x) ==
*(*(a+y)+x)
ponieważ *(a+y) == a+y*ColumnCount to:
powyższe musi być równe: *(a+y*ColumnCount+x)
A to oznacza że albo należy 8.3.4/7 ignorować albo nie jest to UB
Ponieważ wg tego punktu, dla:
T a[5][5];
zapis a[3][2] jest równoznaczny z a[0][5*3+2]

1

Zwolennikom UB zwracam uwagę, że zaprzeczają istnieniu memcpy().

   int tab[2][3] = {{1,2,3},{4,5,6}};

   int bat[2][3];

   memcpy(bat, tab, 6*sizeof(int)); // horror! horror! UB! UB!
3

Nic nie zaprzeczamy (chociaż tam powinno być sizeof(tab)). Standard zezwala na dostęp do zmiennej dowolonego typu jako char*.

Gdybyś zapisał memcpy(&bat[0][0], &tab[0][0], sizeof(int)*6) to można by twierdzić, że to UB.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.