Dziwne zachowanie kompilatora w AVRGCC.

May 04, 2004 90 Replies

Ehlo.



Jako ze guru wiedzy na temat C jeszcze nie jestem (ale zamierzam ;-))) ), wynalazlem pewien problem ktory mnie meczy. Mianowicie w programie definiuje znaki LCD za w nastepujacy sposob:



definicje znakow:



static unsigned char __attribute__ ((progmem)) polish_chars[]={



0x0C,0x04,0x06,0x0C,0x04,0x04,0x0E,0x00, //0 - l [...]
0x00,0x04,0x0E,0x10,0x10,0x11,0x0E,0x00}; //5 = c


for (unsigned char f=1;f<64;f++) { lcd_port=pgm_read_byte(&polish_chars+f); data_port |= (unsigned char)_BV(rs); nop();nop();nop(); data_port |= (unsigned char)_BV(en); nop();nop();nop(); data_port &= (unsigned char)~_BV(en); data_port &= (unsigned char)~_BV(rs); lcd_delay_short(); }



No i problem. Mianowicie wartosc wyliczona przez (&polish_chars+f) w pierwszym odczycie daje poprawnie wartosc 0x22, jednak w kolejnym kroku kompilator zwieksza Z nie o jeden jak powinien lecz o 0x31 (cos jakby wartosc '1' w ASCII). Popelniam jakis blad ??



Za to ten programik dziala poprawnie. Z jest zwiekszane o 0x01.



unsigned char f=0; int g; g=(&polish_chars); while (f<64) { lcd_port=pgm_read_byte(g+f); data_port |= (unsigned char)_BV(rs); nop();nop();nop(); data_port |= (unsigned char)_BV(en); nop();nop();nop(); data_port &= (unsigned char)~_BV(en); data_port &= (unsigned char)~_BV(rs); lcd_delay_short(); f++; }



Moze mi ktos wytlumaczyc czy to ja robie zle czy co ? Z gorki dzieki.


Juz sam wiem, olalem warninga kompilatora i zapomnialem o zrzutowaniu wskaznika na integera ;-)

powinno byc lcd_port=pgm_read_byte(((int)&polish_chars)+f); I dziala ;-)

U¿ytkownik "Milosz Skowyra" snipped-for-privacy@wp.pl napisa³ w wiadomo¶ci news: snipped-for-privacy@wp.pl...

a nie mogl bys tej tablicy jakos po ludzku indeksowac ? :-)

polish_chars[ f ]

tak jest chyba najbardziej naturalnie

- w takim ukladzie moze ci adrument zwiekszac w sizeof(polish_chars), wyrzuc ten &. Wtedy argumentami pgm_read_byte beda kolejne adresy zaczynajac od &polish_chars[f]. Mozna tez tak zapisac. W C dodanie liczby n do pointera tworzy pointer przesuniety o n - elementow tablicy. Ty dodajesz liczba do adresu calej tablicy, a nie do adresu jej pierwszego elementu.

Podsumowujac:

&polish_chars[f] - adres f-tego elementu tablicy polish_chars+f - tyle samo &polish_chars+f - adres f-tej tablicy

Krzysiek Rudnik

nie jestem specem, ale kiedy¶ chcia³bym byæ. proszê mnie o¶wieciæ dlaczwgo "int" ? przecie¿ tablica by³a definiowana jako "unsigned char" "f" równie¿, sk±d i po co " int " ?

Bo sa dwa problemy: a) .. '51 .. kto wie jaki rozmiar ma wskaznik, byc moze tylko 1 bajt ? Wiec wskaznik plus char moze sie ograniczac do 256.

b) kolega przekombinowal [a raczej nie przeczytal pewnej ksiazki :-)] polish_chars+f powinno dzialac dobrze. ale &polish_chars wskazuje na cala tablice i ma rozmiar jej calej, wiec &polish_chars+1 to adres calej kolejnej tablicy, a nie drugiego elementu ..

J.

Czytam powoli... i podpieram sie dodatkowo kieszonkowym leksykonem C++. Tyle tylko ze nie dopisalem kontekstu programu. Po prostu mam tablic definicji znakow kilka i dodatkowo sa troche porozrzucane po flashu. Korzystajac z jednej procedury chce wczytac jedna wybrana z nich. Dlatego tak mi bylo wygodniej. Poza tym ten zapis jest dla mnie bardziej czytelny. Ale moze sie myle. Ja go rozumiem tak: pgm_read_byte(((int)&polish_chars)+f); odczytaj z pamieci ROM bajt z adresu (gdzie adres=adres poczatku tablicy polish_chars, powiekszony o f). Poniewaz pgm_read_byte() operuje na int to rzutuje polish_char na inta i dodaje f jako char-a. W programie dla jednej tablicy co prawda wystarczylby char, ale dla kilku juz nie ;-)

Dziala, zezarl 20 bajtow wiecej ;-)

Zaraz... bo sie pogubilem. Zalozmy ze mam tablice char tab[20]; I teraz zeby dostac sie do 10 elementu moge uzyc: a) char x=tab[9] b) Znalezc adres tablicy i dodac offset czyli char x=(((int)&tab)+10); c) Wskaznik na poczatek tablicy i zwiekszyc go o 10 i odczytac. char *x;

*x=&polish_chars; *x+=10;

Jesli jest tak jak piszesz o rozmiarze to dlaczego sizeof(&polish_chars) daje 2 ? To znaczy dla mnie to jasne bo wynikiem jest adres ktory jest integerem wiec ma rozmiar 2. Myle sie ??

Tu sie zgodze.

Tylko ze nie bardzo chce dzialac ;-) W R14 i R15 jest adres poczatku tablicy.

MOV R31,R15 MOV R30,R14 ;to niby poprawnie, do Z wpisuje pocz.tablicy LD R24,Z+ ;w dziwny sposob zwieksza Z o jeden i czyta ;bajt z RAMU spod adresu poczatku tablicy w ROM MOV R14,R30 ;zrzuca adres tablicy +1 MOV R15,R31 MOV R30,R24 ;i gdyby nie wykonal tego to LPM odczytalo by CLR R31 ;poprawna wartosc, a tak dostajemy krzaczki. LPM

Zgadza sie. Co prawda po przeczytaniu kilku postow mam maly metlik, ale sprobuje usystematyzowac. &polish_chars - to adres poczatku tablicy. Jesli zrobie *x=&polish_chars to wskaznik *x wskazuje na poczatek tablicy. Poprawnie ??

Dzieki... dobrze mysle tylko nie potrafie tego jeszcze dobrze w C zapisac ;-(

Ta dziwnosc moze byc sprytnoscia - jesli rozpoznal ze w kolejnych obiegach petli siegasz do kolejnych komorek pamieci, to jest to prawidlowa kompilacja.

A nie przekombinowales znowu ? Moze masz lcd_port=pgm_read_byte(polish_chars[f]);

J.

Czytac szybciej, szczegolnie rozdzial arytmetyka wskaznikow :-)

Ogolnie C dziala tak, ze (wsk+liczba) ma wartosc liczbowa adres(wsk) + liczba*sizeof(*wskaznik)

Innymi slowy - trzeba pilnie uwazac na typ na ktory wskaznik wskazuje, bowiem dodajemy rozne wartosci. Ale to jest dla wygody, dzieki temu t[i] jest dokladnie tym samym co *(t+i)

Owszem, z tym ze :

- wystarczy pgm_read_byte(((int)polish_chars)+f) - polish_chars i &polish_chars to sa te same adresy, roznia sie natomiast wielkoscia obiektu wskazywanego [co tu nie ma znaczenia]

- w innym procku relacje miedzy wskaznikiem a integerem moga byc inne, wiec zaleca sie nie dokonywac takiej konwersji.

- akurat w AVR byc moze ta funkcja faktycznie ma parametr int, a nie char*.

A to ciekawe. Zglos moze te uwagi supportowi, moze poprawia.

Nie - tak to oliczyles adres [zly!], nie pobrales natomiast zlokalizowanej tam danej, czemu sluzy *

x=*(((int)&tab)+9) a dokladniej, to by wypadalo napisac

x=*( (char*) (((int)&tab)+9));

to samo moglbys zapisac jako

x=*( (char*) (((int)tab)+9)); x=*( tab+9 );

Czy nawet 9[x] - co z definicji wynosi *(9+tab)

Tu glupoty jakies piszes:-)

char *x;

OK, x jest wskaznikiem, czyli adresem, komorki pamieci zawierajacej znak. x jest oczywiscie zmienna, czyli zajmuje dwa bajty w tym procku. x chwilowo jest niezainicjowany, czyli wskazuje w losowo wybrane miejsce pamieci [starsze C wymagaly zeby takie zmienne globalne byly wstepnie zerowane].

*x=&polish_chars;

To jest glupota totalna, bo w ten jeden znak _wskazywany_ przez x [czyli chwilowo w jakies losowe miejsce w pamieci :-)] usilujesz wpisac adres tablicy - czyli pewnie tylko mlodszy bajt adresu.

*x+=10;

A niniejszym usilujesz do znaku wskazywanego przez x dodac 10.

Z sensem wygladaloby to tak: char *x; //wskaznik x=&polish_chars; //teraz wskazuje na poczatek tablicy x+=3 //teraz wskazuje na 4 znak tablicy

*x+=5 // a teraz ten znak powiekszamy o 5

Gdyby w tablicy bylo "ABCDEFGHI", to po wykonaniu zrobiloby sie "ABCIEFGHI" ... gdyby to nie byl ROM.

Bo polish_chars jest tablica, i rozmiar ma chocby i 64. Ale &polish_chars jest juz tylko adresem, a ten liczy 2 bajty.

Natomiast kompilator pamieta na co &polish_chars wskazuje, wiec &polish_chars+1 to bynajmniej nie jest ten sam adres co polish_chars+1.

Nawiasem mowiac - moglbys to zrobic tak:

char * dp;

dp=polish_chars; // lub jak wolisz dp=&polish_chars ; for (unsigned char f=1;f<64;f++) { lcd_port=pgm_read_byte(dp); dp++; // lub wrecz lcd_port=pgm_read_byte(dp++);

data_port |= (unsigned char)_BV(rs); [...] }

Tracisz 2 bajty RAM na dodatkowa zmienna [zreszta niekoniecznie], ale byc moze zyskujesz cos na programie - o ile powiekszenie wskaznika jest w danym procku prostsza operacja niz dodanie dwoch liczb 16 bit..

A swoja droga - o ile kompilator pozwala zdefiniowac taka tablice w obszarze pgm, to imho powinien poprawnie komplilowac po prostu polish_chars[f] - albo cos o tym pisze w faq, albo trzeba serwis powiadomic..

J.

[.....]

Na mój gust to są dwa różne adresy - polish_chars jest adresem obszaru pamięci w którym umieszczone są obiekty typu char (czyli polish_chars to jest po prostu typu char *), natomiast &polish_chars jest adresem adresu w/w obszaru czyli mówiąc inaczej jest typu char **, albo jescze inaczej, jest wskaźnikiem na wskaźnik. :-)

[.....]

Nie chce mi się odpalać kompilatora ale jestem pewien że żadna z powyższych konstrukcji nie zadziała tak jak oczekiwano. :-)

Drugie wyrażenie będzie działać, pierwsze nie zawsze. Ale one nie są równoważne tym dwóm wcześniejszym.

Poza tym w zasadzie warto byłoby sprecyzować w tej dyskusji (i w świetle tego co napisałem na samym początku) co to znaczy "znaleźć adres tablicy". Bo jeśli chodzi o adres obszar pamięci w którym znajdują się przechowywane obiekty, to wartość zmiennej tab (pisanej bez żadnych indeksów) jest właśnie tym adresem i nic nie trzeba szukać.

[.....]

^- powinno być tez tego ampersanda

Regards, /J.D.

Nie - pamietaj ze polish_chars jest tablica ! wiec "jej wartosc liczbowa" jest adresem pierwszego elementu, i pokrywa sie z adresem calosci.

Powinny.

Zgodze sie - potrafia byc niuanse. Ale czesto int sie pokrywa ze wskaznikiem - i wtedy powinno zadzialac.

Poza tym drobnym zastrzezeniem - sa.

Dokladnie. Wiec tab==&tab - choc sa miedzy nimi roznice typu i nie zachowuja sie tak samo.

Aha - to dotyczy rzeczywistej deklaracji tablicy, bo deklaracja w naglowku funkcji jest wskaznikiem..

J.

[.....]

Rzeczywiście, masz rację co widać na załączonym przykładzie:

#include <stdio.h>

int main(int argc, char **argv) { char tab1[10], *tab2, **tp1, **tp2;

tp1 = (char**) &tab1; tp2 = &tab2; printf("tab1=%p, tp1=%p, *tp1=%p\n", tab1, tp1, *tp1); printf("tab2=%p, tp2=%p, *tp2=%p\n", tab2, tp2, *tp2);

tab2 = tab1; printf("tab2=%p, tp2=%p, *tp2=%p\n", tab2, tp2, *tp2);

return 0; }

Niby do tab1 i tab2 można odwoływać się w ten sam sposób, tj. przy pomocy indeksów lub arytmetyki wskaźników ale jednak są pewne niuanse w działaniu operatora &. :-) Używając C trzeba być jednak czujnym przez cały czas. :-)

Nie bardzo rozumiem. Może jakiś przykład?

Regards, /J.D.

Wiesz, w C tablice indeksuje sie poczynajac od 0. :-) Poza tym postinkrementacji nalezy uzywac tylko gdy naprawde trzeba; w przeciwnym przypadku nalezy uzyc preinkrementacji: ++f. W tym przypadku nie ma to znaczenia, ale jesli zaczniesz programowac w C++, to zobaczysz roznice, gdy sobie przeciazysz ++.

Scisle rzecz biorac nie popelniasz bledu, ale napisales kompilatorowi nie ten program o ktorym myslales. :-) Kazesz mu wziac adres poczatku f-tej _tablicy_, a nie adres f-tego _elementu_ tej tablicy. Natomiast to

0x31 do niczego mi nie pasuje: masz 49 elementow w tablicy?

No chyba zartujesz. :-) Dlaczego nie uzyjesz

const unsigned char* g = polish_chars;

?

Pozdrawiam Piotr Wyderski

PS. Cos wspominales o przejsciu na uprawe marchewki. ;-)))

Wcale nie, to Ty nie przeczytales tego warninga i nie zamieniles integera na char*. Przykro mi, dopisujac "brakujace" rzutowanie Ty nie naprawiles tego programu, Ty zgwalciles kompilator. :->

Pozdrawiam Piotr Wyderski

Tia, w probnej wersji bylo tylko 49 elementow. Teraz juz kumkam.

Wlasnie kombajn do zbiorow konstruuje ;-))

Bez przesady, szybciej bedzie mu zrobic adiw z,1 bo nie bedzie przelewal Z w rejestry tam i spowrotem.

Dokladnie tak, bo tak sugerowal Andy ;-)

Robi sie... problem tylko ze co innego czytalem a co innego rozumialem ;-((( Znaczy sam sie w pole (marchewki) wyprowadzilem.

[...]

I juz kumam. Nie wiem dlaczego ale caly czas lazilo mi po glowie ze jak operujemy na wskazniku to na wskazniku, czyli jak mamy char *x; to wszystkie operacje tycza sie *x. Teraz wiem ze operacje na zawartosci wskazywanej zmiennej odbywa sie przez dereferencje. Jeszcze ostatnia niejasnosc... char x[20],y[20],a; char *xptr; xptr=&x; //###### a=*xptr; //w a laduje element x[0]

xptr=&x+1; //###### a=*xptr; //w a laduje element y[0]

xptr=x; a=*xptr; //w a laduje element x[0]

xptr=x+1; a=*xptr; //w a laduje element x[1]

juz mysle dobrze czy nadal gdzies blad w logice ?? I jeszcze jedno. Kompilacja tego powyzej daje w liniach oznaczonych ##### warninga "main.c:16: warning: assignment from incompatible pointer type". Mam racje ze chodzi o to ze 2 bajtowy pointer nie miesci sie w char-ze ??

To chyba wlasnie pojalem ;-)

Jak dziala polish_chars[f] to juz podalem ;-)

Nic na sile ... wszystko mlotkiem :-)

J.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required