Ukazatel na vícerozměrné pole

nm

Ukazatel na vícerozměrné pole
« kdy: 08. 06. 2011, 13:40:26 »
Teď se učím čisté Céčko a narazil jsem na jednu takovou věc. Přenáším ukazatel na vícerozměrné pole (konkrétně název vícerozměrného pole, což je v podstatě ukazatel na začítek pole) jako argument pro funkci, kde ho přebírá standardní ukazatel. S jednorozměrným polem vše funguje dobře. Ale jakmile jde o vícerozměrné pole, překladač zahlásí error. Zde je ukázkový kód:

Kód: [Vybrat]
int vymaz_ch(char *pch) {
int i, j;

for (i = 0; i < 100; i++) {
for (j = 0; j < 50; j++) {
pch[i][j] = 0;
}
}

return 0;
}

int main(void) {
char ch[100][256];

vymaz_ch(ch);

return 0;
}

No a tohle mi hlásí překladač gcc v Eclipse:


**** Build of configuration Debug for project c_test ****

make all
Building file: ../src/c_test.c
Invoking: GCC C Compiler
gcc -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/c_test.d" -MT"src/c_test.d" -o"src/c_test.o" "../src/c_test.c"
../src/c_test.c: In function ‘vymaz_ch’:
../src/c_test.c:16:10: error: subscripted value is neither array nor pointer
../src/c_test.c: In function ‘main’:
../src/c_test.c:26:2: warning: passing argument 1 of ‘vymaz_ch’ from incompatible pointer type
../src/c_test.c:11:5: note: expected ‘char *’ but argument is of type ‘char (*)[256]’
make: *** [src/c_test.o] Error 1


Zkoušel jsem googlovat jako blázen, ale všude se píše pouze o ukazateli na jednorozměrné pole. A tak jsem se chtěl zeptat, jestli je možné vytvořit ukazatel na vícerozměrné pole nebo jak se takováhle věc prakticky řeší?


Nassir

Re: Ukazatel na vícerozměrné pole
« Odpověď #1 kdy: 08. 06. 2011, 14:41:00 »
Moje prakticke skusenosti s C su skoro 10 rokov stare, ale zakladom je pochopenie operatora [].
Kam v pameti sa ma skocit ked poviem pch[20][30] ? Ked nevies rozmer prveho ani druheho pola, tak netusis a program to takisto netusi.
Rieseni je niekolko:
1) ako parameter funkcie vymaz_ch musis davat zasa len pole aj s rozmerom - potom to mozno bude fungovat. Pravdepodobne vsak nie a to koli moznosti 2.
2) zapis char ch[100][256] znamena jednorozmerne pole o 100 ukazovateloch na polia 256 znakov. Takze touto definiciou si si alokoval len to prve prazdne pole s dlkou 100 pointrov (takze asi 400 bajtov na 32 bit procesore ak sa nemylim - nestrielajte ak sa mylim). Az po naplneni kazdeho pointra novym 256 bajtovym polom(jeho adresou samozrejme) s nim mozes pracovat. A to sa staticky bude robit dost ztuha.
3) Urobis si jedno linearne pole dlzky 100*256 a sam si don budes vypocitavat vektor ako pole(i*100+j). Usetris aj na pameti, ale nebude to take pekne.
4) Za desat rokov sa moje vedomosti vyparili a da sa to jednoducho nadefinovat - potom sa ospravedlnujem za zavadzanie a pockam si na riesenie spolu s tebou. :)

j.

Re: Ukazatel na vícerozměrné pole
« Odpověď #2 kdy: 08. 06. 2011, 14:52:53 »
Zkuste:
int vymaz_ch(char **pch)


alefo

Re: Ukazatel na vícerozměrné pole
« Odpověď #3 kdy: 08. 06. 2011, 15:09:28 »
Nesedia vam datove typy. Char * sa chape bud ako pointer na char alebo ako jednorozmerne pole.

Kompilator si nevycuca z prsta, ze cislo ulozene v chlieve s adresou, ktora je ulozena v pch sa ma chapat ako pociatok pola, ku ktoremu sa bude pristupovat indexami.

Dvojrozmerne pole sa decayuje (rozklada? rozpada?) na pointer na pointer na typ, aby to bolo korektne, a to vam otrieskava o hlavu kompilator, ktory vidi prichadzat char ** (presnejsie pointer na 256prvkove pole charov), lenze deklarovany mate pointer na char.

nhx

Re: Ukazatel na vícerozměrné pole
« Odpověď #4 kdy: 08. 06. 2011, 15:12:12 »
Dvourozměrné pole v C je vlastně pole pointerů na pole prvků. Proto je správně int vymaz_ch(char **pch), nikoliv int vymaz_ch(char *pch).


Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Ukazatel na vícerozměrné pole
« Odpověď #5 kdy: 08. 06. 2011, 15:22:11 »
Jenže to nefunguje, protože dvojrozměrné pole ve skutečnosti není pointer na pointer, je to plochý linearizovaný dvourozměrný paměťový prostor.

Jak se to řeší jsou dvě možnosti,
- buďto pracovat s jednorozměnrným polem (tzn. pole přetypovat na jednorozměrné pole popř. pointer),
- nebo jako parametr dát dovurozměrné pole. Ale aby šlo správně počítat, tak se musí vědět vnitří rozměr pole, tzn. arguemnt definovat ve tvaru
void fce(char arg[][rozmer])

Pokud se nechceš vázat na konkrétní rozměr pole:
template<int rozmer>
void fce(char arg[][rozmer])





1234

Re: Ukazatel na vícerozměrné pole
« Odpověď #6 kdy: 08. 06. 2011, 15:23:00 »
Dvourozměrné pole v C je vlastně pole pointerů na pole prvků. Proto je správně int vymaz_ch(char **pch), nikoliv int vymaz_ch(char *pch).

Kez by to bylo tak jednoduche.
Moznost je vymaz_ch(char (*pch)[256]) pro fixni rozmery pole [][256]. Jine (ne vzdy koser) moznosti lze nalezt v http://www.ibiblio.org/pub/languages/fortran/append-c.html nebo http://www.lysator.liu.se/c/c-faq/c-2.html. Ony pole fixni velikosti a dynamicky alokovane se ne vzdy chovaji stejne. ;)

Galli

Re: Ukazatel na vícerozměrné pole
« Odpověď #7 kdy: 08. 06. 2011, 15:24:01 »
Pouzil jsem dynamicky pole a zda se ze funguje
Kód: [Vybrat]
void vymaz_ch(char **pch) {
int i, j;

for (i = 0; i < 100; i++) {
for (j = 0; j < 50; j++) {
pch[i][j] = 0;
}
}

return 0;
}

int main(void) {
char **ch;
int vyska = 100, delka = 50,a,b;
ch = (char **)malloc(vyska*(sizeof(char *)));
            for(a=0;a<vyska;a++)
        {
            ch[a]=(char *)malloc(delka*(sizeof(char)));
        }

vymaz_ch(ch);

return 0;
}

Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Ukazatel na vícerozměrné pole
« Odpověď #8 kdy: 08. 06. 2011, 16:04:34 »
Jo, to je taky možnost. Má to nevýhodu ve výkonu, jednak alokace, jednak to pole není souvislé (selhává prefetch atd...), jednak to žere extra paměť na ukazatele na jednotlivé řádky pole.

Má to ale zas své výhody (jednoduché přehazování řádků, možnost je udělat různě dlouhé), je to prostě něco za něco.

nm

Re: Ukazatel na vícerozměrné pole
« Odpověď #9 kdy: 08. 06. 2011, 16:55:48 »
Pokud pouziju toto:
Zkuste:
int vymaz_ch(char **pch)
tedy celkovy kod vypada takto:
Kód: [Vybrat]
int vymaz_ch(char **pch) {
   int i, j;

   for (i = 0; i < 100; i++) {
      for (j = 0; j < 50; j++) {
         pch[i][j] = 0;
      }
   }

   return 0;
}

int main(void) {
   char ch[100][256];

   vymaz_ch(ch);

   return 0;
}
Pak mi preklad po tomto prikazu:

gcc c_test.c -o test

zahlasi toto:

c_test.c: In function ‘main’:
c_test.c:26:4: warning: passing argument 1 of ‘vymaz_ch’ from incompatible pointer type
c_test.c:11:5: note: expected ‘char **’ but argument is of type ‘char (*)[256]’

Preklad sice projde, ale pri spusteni ( ./test ) mi program zahlasi:

Segmentation fault

takze to reseni neni.

Martingt89

Re: Ukazatel na vícerozměrné pole
« Odpověď #10 kdy: 08. 06. 2011, 17:08:35 »
Uplne najjednoduchsie je pouzit jednorozmerne pole a pristupovat k nemu ako dvojrozmernemu
Kód: [Vybrat]
char pole[100*256];
vymaz_ch(pole);
.
.
.
int vymaz_ch(char *pch){
for (i = 0; i < 100; i++) {
      for (j = 0; j < 50; j++) {
         pch[i*256 + j] = 0;
      }
   }
}


Jouda

Re: Ukazatel na vícerozměrné pole
« Odpověď #11 kdy: 08. 06. 2011, 17:44:45 »
Teď se učím čisté Céčko a narazil jsem na jednu takovou věc. Přenáším ukazatel na vícerozměrné pole (konkrétně název vícerozměrného pole, což je v podstatě ukazatel na začítek pole) jako argument pro funkci, kde ho přebírá standardní ukazatel. S jednorozměrným polem vše funguje dobře. Ale jakmile jde o vícerozměrné pole, překladač zahlásí error.
A volání vymaz_ch(&ch[0][0]) nefunguje?


nm

Re: Ukazatel na vícerozměrné pole
« Odpověď #12 kdy: 08. 06. 2011, 17:56:07 »
Tak si prave ctu knizku od Herouta: Pavel Herout - Ucebnice jazyka C (z roku 1994) a zda se, ze jednim z reseni je opravdu toto:
Pouzil jsem dynamicky pole a zda se ze funguje
Kód: [Vybrat]
void vymaz_ch(char **pch) {
int i, j;

for (i = 0; i < 100; i++) {
for (j = 0; j < 50; j++) {
pch[i][j] = 0;
}
}

return 0;
}

int main(void) {
char **ch;
int vyska = 100, delka = 50,a,b;
ch = (char **)malloc(vyska*(sizeof(char *)));
            for(a=0;a<vyska;a++)
        {
            ch[a]=(char *)malloc(delka*(sizeof(char)));
        }

vymaz_ch(ch);

return 0;
}
Ma to vyhodu v tom, ze si muzu dynamicky menit delku kazde vysky (po uprave kodu). Ale ma to jednu nevyhodu. Protoze podle toho, co pise Herout, tak se jednotlive radky dvourozmerneho pole nemusi alokovat v pameti hned za sebou (a zpravidla se tak opravdu nealokuji), takze je pak treba problem v tom, kdyz pristupuji na pole pomoci preteceni indexu. Takze napriklad, kdyz pouziji toto:
Kód: [Vybrat]
pch[0][51] = 1;tak vlastne prakticky v realu zpravidla nezmenim hodnotu na pch[1][0] = 1 , ale zmenim nejakou pamet, ktera je alokovana pro neco jineho.

Druha nevyhoda tohoto reseni, jak pise Herout, je ta, ze kod je pak pomalejsi nez jine reseni, jako je napriklad reseni pomoci jednorozmerneho pole, jako je treba toto (a jak pise take Martingt89):
3) Urobis si jedno linearne pole dlzky 100*256 a sam si don budes vypocitavat vektor ako pole(i*100+j). Usetris aj na pameti, ale nebude to take pekne.
Tohle reseni ma zase tu nevyhodu, ze musi byt staticke, coz znamena vedet rozmery pole pri prekladu. Mozna by pak pomohlo dynamicky alokovat jednorozmerne pole, tedy asi takto (pokud se nepletu):
Kód: [Vybrat]
#include <stdlib.h>

void vymaz_ch(char *pch, int vyska, int delka) {
int i, j;

for (i = 0; i < vyska; i++) {
for (j = 0; j < delka; j++) {
pch[i * delka + j] = 0;
}
}
}

int main(void) {
char *ch;
int vyska = 100, delka = 50;

ch = (char *) malloc(vyska * sizeof(char) * delka);

vymaz_ch(ch, vyska, delka);

return 0;
}
A to by snad melo byt rychlejsi nez alokovat dvourozmerne pole. Ma to zase tu vyhodu (mozna jenom pseudovyhodu), ze je pamet alokovana jako jeden celek, to znamena vysky hned za sebou, i kdyz asi nemuzeme pouzit indexy pro dvourozmerne pole jako pch[0][51]. To by asi neslo. A ma to zase tu nevyhodu, ze velikost delky musi byt pro kazdou vysku stejna. To by slo zase po uprave kodu zmenit, ze bysme meli ruznou delku pro kazdou vysku, ale pak by se to asi muselo resit treba novym polem pointeru, ktere by ukazovaly na zacatek kazde vysky. A tim bysme se vlastne dostali k one alokaci pameti pro dvojrozmerne pole. Tezko rict, ktere reseni by pak bezelo rychleji.

Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Ukazatel na vícerozměrné pole
« Odpověď #13 kdy: 08. 06. 2011, 18:30:20 »
Zkus si přečíst něco o polích v C. &ch[0][0] je typu char*, jak by mohlo fungovat předání do funkce s argumentem char **.

Typ pole je ekvivalentní s const *T. Dvourozměrné pole je tedy defakto (const **).

Operátor [a] je vlastně *(pole + a*sizeof(T)).

Klíčový rozdíl mezi polem a pointerem je v tom, že sizeof(char[20]) je dvacet, zatímco sizeof(const char*) je rovno (sizeof void *) (4 nebo 8) . Proto nemůže fungovat práce
s polem předaným do funkce jako char **, protože dovjrozměrné pole je ukazatel na jednorozměrné pole, kde sizeof toho jednorozměrnýho pole jde zjistit z deklarace toho pole.
Ale v tý funkci je sizeof toho vnitřku zas jen velikost ukazatele na char. Proto to kompilátor odhchytí, protože to jsou díky tomu nekompatibilní typy.

Jediná možnost je tedy buďto použít dynamickou alokaci každýho řádku, kde to je pak,
takže se provádí o jednu dereferenci navíc

a->radka-> elementy radku
  ->radka-> elementy radku
  ->radka-> elementy radku

A nebo v deklaraci funkce dát alespoň správný typ pro všechny úrovně pole kromě poslední. tzn
*(char c[20])
nebo
char c[][20]

A popř. pro každej rozměr vygenerovat vlastní fci šablonou.

---
nm: spojité pole s vedlejším polem ukazující na začátky řádky by bylo rychlejší, kvůli prefetchi, menším výpadkům v cache a rychlejší alokaci.






Jouda

Re: Ukazatel na vícerozměrné pole
« Odpověď #14 kdy: 09. 06. 2011, 00:28:15 »
Zkus si přečíst něco o polích v C. &ch[0][0] je typu char*, jak by mohlo fungovat předání do funkce s argumentem char **.
Proč by to mělo fungovat? Nejspíš proto že podle prvního příspěvku je funkce tak definovaná:
Kód: [Vybrat]
int vymaz_ch(char *pch) {