wskaźniki a tablice wielowymiarowe

5

Odskakując trochę od dyskusji, szperam sobie co tam ludzie na necie piszą o tym. Znalazłem m.in. taki cytat na grupie ISO C++ Standard - Discussion (wytłuszczenie moje):

If you just want to flatten a multidimensional array, there's also taking the address of the first object, and using that as the basis for address arithmetic. But according to Clang, and my intuition of the standard, this also isn't allowed in constexpr since address arithmetic is only allowed in an immediate array, and subsequent arrays in the multidimensional object are past-the-end. This is a standard flaw in my humble opinion, but not a defect. Anyway GCC allows it without complaint, at least for this test: [...]

Źródło: https://groups.google.com/a/isocpp.org/forum/#!searchin/std-discussion/multidimensional/std-discussion/GxGgAaMoO40/nS26dro7ljkJ
Autorem jest David Krauss, którego nazwisko znalazłem przy propozycjach do standardu z paru ostatnich lat (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/).

Czyli kolejny głos na to, że to UB wg standardu, i jednocześnie na to, że raczej powinno się to konkretne UB zignorować, bo (jak to czasem bywa), standard swoje, a implementacje i programiści swoje ;) (i dobrze, inaczej by tak jakoś nudno było)

1

Z drugiej strony nawet K&R ponoć tak to rozumieli jak @Azarien.

http://stackoverflow.com/a/29734680

/me: hejtuje UB

1
adrian17 napisał(a):

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.

Błąd logiczny. Jest dokładnie odwrotnie. Jeśli sto reguł mówi, że coś jest niezdefiniowane (czyli sto reguł nie definiuje zachowania), a choćby jedna je definiuje, to zachowanie jest zdefiniowane.
Oczywiście standard powinien być spisany na tyle precyzyjnie, żeby takich sytuacji nie było. Ale są.

3

No nic, czas iść spać :) Chyba możemy się zgodzić, że się nie zgadzamy, a przynajmniej, że standard się nie zgadza z rzeczywistością (mild shock). Dzięki za dyskusje Panowie :)

0

...ja chyba właśnie zostałem przekonany. W ogólnym przypadku to jest UB, ale w przypadku tablic wielowymiarowych fakt, że standard jednoznacznie określa ich rozkład w pamięci, definiuje rezultat który otrzymamy.

W każdym razie niezależnie od tego jaka odpowiedź jest poprawna, zgadzam się z Davidem Kraussem że standard powinien to lepiej określić.

0

Rozumiem, że ten wątek ma na celu udowodnienie, że istnieją jednak języki bardziej skopane niż JavaScript?

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).

Oboje macie racje w detalach wasze racje są sprzeczne. Racja Azariena jest niebezpieczna.. dogłębnie wyjaśnie to później bo teraz nie mam czasu. Poza tym wyjaśnie róznice co do wpływu na szybkość działania programu, adresowanie elementów tych tablic itd. Wklejam kod dla autora tematu żeby się zaznajomił ze wskaźnikami. Może sobie poekspetymentować, zamiast maloka wstawić wskaźnik do pierwszej i sprawdzi czy wyjdzie tak samo. Wskaźniki są jak zapałki w rękach dziecka. Prędzej czy póxniej wywołują pożar.

 //---------------------------------------------------------------------------
// Example program
#include <string>
#include <assert.h>
#include <stdio.h >
#include <stdlib.h>
#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
int main(int argc, char* argv[])
{
char tab_azariena[3][5][7];
// zmienna pomocnicza do ustalenia zawartości kolejnych komórek tabeli
// Po koleji znakami ASCII od 1.2.3.4.5.. przez ABCD..abcd (między nimi kilka znaków międzynarodowych)
printf("Prodgram drukuje dwie tabele[3][5][7] jedna a[] to stosowy klasyk druga e[] utworzona z 3 list wskaznikow");
char num = '1';

for (int z = 0; z < 3; z++) {
	for (int y = 0; y < 5; y++) {
		for (int x = 0; x < 7; x++) {
        	tab_azariena[z][y][x] = num++;
            }
        }
    }
// dla pewności na ostatnim logicznie polu znak '\0'
tab_azariena[2][4][6] = '\t';
num = '1';

char ***tab;
tab = (char***)malloc(3*sizeof(char**));
assert(tab!= NULL);
for (int z = 0; z < 3; z++) {
    char **az = (char**)malloc(5*sizeof(char*));
    assert(az!= NULL);
	tab[z] = az;
	for (int y = 0; y < 5; y++) {
    	char *ay = (char*)malloc(7*sizeof(char));
        assert(ay!= NULL);
    	tab[z][y] = ay;
		// wypełnianie tablic liczb z memset jest kilkukrotnie szybsze niż naiwna pętla a to dzięki ASM x86: "REP SETB"
        ::memset((void*)(tab[z][y]),'0',5);
        //ale można i tak i pętlą
		for (int x = 0; x < 7; x++) {
        	tab[z][y][x] = num++;
        	}
        }
    }
// dla pewności na ostatnim logicznie polu znak '\0'
tab[2][4][6] = '\t';

for (int z = 0; z < 3; z++) {
        	printf("Warstwa tab[%d]:\n",z);
	for (int y = 0; y < 5; y++) {
		for (int x = 0; x < 7; x++) {
        	printf("a[%c] n[%c],",tab_azariena[z][y][x],tab[z][y][x]);
        	}
        	printf("\n");
        }
    }

printf("W kolejnej linijce wydrukuje tab_azariena jako liniowy wycinek pamieci:\n");
printf("%s",(char*)tab_azariena);
printf("\n");
printf("Koniec!\n");
system("pause");
}
//---------------------------------------------------------------------------

Można go przetestować u siebie albo na czymś w stylu(?):
http://cpp.sh/

Program dowodzi tezy że Azarien ma racje, w kolejnym poście dowiode tezy że nawet jeśli ma racje to nic dobrego z niej nie wynika. A w istocie taka świadomość może rodzić więcej problemów niż pożytku.

Nigdy nie wierz kobiecie, tablicy wielowymiarowej i wskaźnikom.

6

co

1

ogólnie z tablicami wielowymiarowymi jest tak że jeśli mamy np tablice tab[x][y]; to obliczenie miejsca polega na tym (j*y+i)*dlugosc_jednej_zmiennej a więc musi być znana długość wszystkich indeksów oprócz tego najbardziej z lewej
ponadto ponieważ chyba [] ma większy priorytet operatorów więc deklaracja wskaźnika powinna wyglądać tak jak to napisałem w kodzie

#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)[index1]; //deklaracja wskaźnika
 
    wsk = tab;//nazwa tablicy jest adresem jej zerowego elementu
 
    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;// wskaźnik możemy używać jak tablice
 
    return 0;
}
 
0
dreammaker napisał(a):

ogólnie z tablicami wielowymiarowymi jest tak że jeśli mamy np tablice tab[x][y]; to obliczenie miejsca polega na tym (j*y+i)*dlugosc_jednej_zmiennej a więc musi być znana długość wszystkich indeksów oprócz tego najbardziej z lewej
ponadto ponieważ chyba [] ma większy priorytet operatorów więc deklaracja wskaźnika powinna wyglądać tak jak to napisałem w kodzie

#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)[index1]; //deklaracja wskaźnika
 
    wsk = tab;//nazwa tablicy jest adresem jej zerowego elementu
 
    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;// wskaźnik możemy używać jak tablice
 
    return 0;
}
 

Dzięki wielkie o to chodziło już wszystko śmiga. Jeszcze raz dzieki.

0

Cieszę się że mogłem pomóc polecam bardzo książkę Symfonia C++ Jerzego Grębosza (najnowsza ma dopisek Standard lecz powstaje jeszcze nowsza) po lekturze tej książki powinieneś wszystko lub prawie wszystko zrozumieć o tym języku

3

Takie posty są powodem, aby można było dawać negatywne oceny postom.

Symfonia C++ jest przestarzała, ale nawet w czasach gdy opisywała w miarę obecny standard C++, twierdzenie, że uczy czegokolwiek poza podstawami kompromituje mówcę.

0

Problem rozwiązałem? Rozwiązałem więc o co wam chodzi? Ja się C++ nauczyłem właśnie z Symfonii a potem jeszcze poprawiłem "liźnięciem" Asemblera żeby mieć lepsze pojęcie 700 osób przeglądało ten wątek i co i ja rozwiązałem problem(zawsze się znajdą malkontenci ) jeśli znacie jakieś lepsze książki to byłbym wdzięczny za polecenie mi jakichś jeśli rzeczywiście są lepsze to chętnie poczytam. I niech Kod będzie z Wami

1 użytkowników online, w tym zalogowanych: 0, gości: 1