Fórum Root.cz

Hlavní témata => Vývoj => Téma založeno: nm 08. 06. 2011, 13:40:26

Název: Ukazatel na vícerozměrné pole
Přispěvatel: nm 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ší?
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Nassir 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. :)
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: j. 08. 06. 2011, 14:52:53
Zkuste:
int vymaz_ch(char **pch)

Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: alefo 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.
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: nhx 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).
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Logik 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])




Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: 1234 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 (http://www.ibiblio.org/pub/languages/fortran/append-c.html) nebo http://www.lysator.liu.se/c/c-faq/c-2.html (http://www.lysator.liu.se/c/c-faq/c-2.html). Ony pole fixni velikosti a dynamicky alokovane se ne vzdy chovaji stejne. ;)
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Galli 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;
}
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Logik 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.
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: nm 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.
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Martingt89 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;
      }
   }
}

Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Jouda 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?

Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: nm 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.
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Logik 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.





Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Jouda 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) {
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: geomodular 09. 06. 2011, 08:41:15
V cistom c-ecku a konkretne pod standardom c99 (c90 a nizsie uz nie) to mozes napisat aj takto:
Kód: [Vybrat]
#include <stdio.h>

int vymaz_ch(int a, int b, char pch[a][b]) {
int i, j;

for (i = 0; i < a; i++) {
for (j = 0; j < b; j++) {
pch[i][j] = 0;
}
}

return 0;
}

int main(int argc, char **argv)
{
char ch[100][256];

vymaz_ch(100, 256, ch);

return 0;
}

Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Logik 09. 06. 2011, 12:29:25
jouda: Jo, to máš pravdu, zapoměl jsem, jak ji definoval prvně.  V tom případě to nemůže fungovat díky tomu, že ve funkci s argumentem char* je nesmysl výraz c[][]. Prostě tímdle způsobem se z dvojrozměrným polem pracovat nedá.
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Ondřej Novák 09. 06. 2011, 14:39:07
V cistom c-ecku a konkretne pod standardom c99 (c90 a nizsie uz nie) to mozes napisat aj takto:
Kód: [Vybrat]
#include <stdio.h>

int vymaz_ch(int a, int b, char pch[a][b]) {
....

Bohužel, nepřeloží se to v C++

Kód: [Vybrat]
ondra@natalie64:/tmp$ g++ test.cpp -o test
test.cpp:2:38: error: array bound is not an integer constant before ‘]’ token
test.cpp:2:41: error: array bound is not an integer constant before ‘]’ token
test.cpp: In function ‘int vymaz_ch(...)’:
test.cpp:3:29: error: ‘a’ was not declared in this scope
test.cpp:3:56: error: ‘b’ was not declared in this scope
test.cpp:3:68: error: ‘pch’ was not declared in this scope
ondra@natalie64:/tmp$

gcc to vezme
Název: Re: Ukazatel na vícerozměrné pole
Přispěvatel: Jouda 11. 06. 2011, 09:07:24
Ono by to hlavně chtělo, aby původní tazatel (nm) napsal co od té funkce přesně očekává (specifikaci, jaké jsou povolené vstupy, ...) a na jakých kompilerech/standardu to má fungovat. Vyřešit to tak, aby naznačená věc v příkladu fungovala, se to dá několika způsoby. Jaký je ten optimální ale nejde bez dalších informací říct...