logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[C][AVR] - definicja wskaźnika na łańcuch w funkcji

mirekk36 28 Lis 2010 16:10 2499 18
REKLAMA
  • #1 8800467
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Witam,

    Nie wiem już - coś się zakręciłem czy jak?

    Co się dzieje , że gdy zdefiniuję tak łańcuch:

    
    char *str = "napis";
    
    int main(void) {
    
      *str = 'p';
    
       lcd_str(str);
    }


    Gdzie *str jest przed main to wszystko działa OK, mogę zmienić "napis" na "papis", ale gdy spróbuję zrobić to tak:

    
    int main(void) {
    
      char *str = "napis";
    
      *str = 'p';
    
       lcd_str(str);
    }


    to już nie udaje mi się ta sama operacja - hmmm muszę gdzieś robić jakiś durny błąd w myśleniu - może ktoś mnie naprowadzić o co chodzi.

    W pierwszy przypadku "napis" zostanie utworzony w pamięci RAM w sekcji gdzie leżą sobie zmienne globalne jak domniemuję ale w drugim przypadku ? hmmm na stosie ? czy gdzie ? zresztą obojętnie gdzie to wskaźnik powinien prawidłowo wskazywać i powinienem mieć chyba możliwość dostępu do tej części pamięci RAM.... chyba że właśnie coś mi się pomieszało ?
  • REKLAMA
  • #2 8800642
    Konto nie istnieje
    Konto nie istnieje  
  • Pomocny post
    #3 8800660
    gaskoin
    Poziom 38  
    Posty: 4159
    Pomógł: 436
    Ocena: 102
    Zmienne lokalne tworzone są na stosie, ale nieważne. Generalnie nie ma sensu tworzyć wskaźnika na literał, bo nie wiadomo jaki ma adres??

    jak już to:

    char tab[] = "napis";
    	char *str = tab;
    	*str = 'p';
    	printf("%s", str);
  • #4 8800672
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    albertb napisał:
    Co znaczy nie udaje? Badź precyzyjniejszy.

    Przepraszam już się poprawiam może tylko mi wydawał się jasny mój przykład, zaraz poprawię.

    albertb napisał:
    Oba kody mają działać. Błąd masz gdzie indziej.

    Na chociażby taką odpowiedź liczyłem i w zasadzie już to powinno mi wystarczyć bo tak jak myślałem musiałem coś namieszać i takie głupie pytanie mi wyszło sorry.

    albertb napisał:
    Tablica znaków zawierająca stałą tekstową zostanie utworzona w obu przypadkach w tym samym miejscu. Zmienia się tylko alokacja wskaźnika.

    Taką miałem nadzieję.

    Pisząc, że się nie udaje operacja w drugi przykładzie miałem na myśli że zamiast napisu "papis" wyświetla mi się cały czas tylko "napis". Tak jakby

    *str = 'p'; 


    zapisywało tą literkę 'p' - no właśnie - nie wiem gdzie.

    Ale już szukam bo musi to być rzeczywiście jakiś mój głupi błąd.
  • REKLAMA
  • #5 8800684
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2203
    Nie do końca koledzy mają rację. Problem wynika z architektury AVR. W przypadku literału, który jest globalny, kod startowy programu w C zajmuje się jego skopiowaniem do RAMu i można utworzyć na niego wskaźnik - będzie to prawidłowy wskaźnik do RAM. W przypadku literału lokalnego nie ma co go kopiować do RAM (na etapie działania skrtyptów startowych ten literał przecież nie jest znany), natomiast wskaźnik zgodnie ze standardem C jest tworzony na adres jaki teoretycznie on zajmuje - czyli jest to wskaźnik do pamięci FLASH. Oczywiście w tym przypadku nieprawidłowy. Ale w powyższym programie jest jeszcze poważniejszy błąd związany z błędnym myśleniem, które doprowadzi do katastrofy niezależnie od architektury. Literał jest stałą i jako taka umieszczona jest w sekcji .text wraz z kodem programu. Tworzysz na nią wskaźnik, a następnie przy pomocy wskaźnika próbujesz ją zmodyfikować - to jest niedozwolone. W taki sposób prowadziłoby to do samomodyfikowalnego kodu. Niedobry pomysł. Aby taki literał zmodyfikować należy najpierw przydzielić mu dynamicznie pamięć, skopiować do tej pamięci, a potem sobie normalnie operować jak na zmiennej. No i właściwie nie ma sensownego powodu, żeby takie literały miały być lokalne.
  • REKLAMA
  • Pomocny post
    #6 8800744
    Konto nie istnieje
    Konto nie istnieje  
  • #7 8800749
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Dlatego już zacząłem grzebać żeby użyć malloc() ... ale teraz widzę, że i takie automatyczne przydzielanie pamięci w małych AVRkach to troszkę przerost formy nad treścią , chyba żeby pisać coś w C++ to może.

    Rozumiem, że tworzy się on w pamięci FLASH także bo MUSI, jednak jakieś skrypty startowe przenoszą go do RAM bo jednak udaje się go pobrać za pomocą wskaźnika z tejże pamięci RAM.... moja funkcja lcd_str może przyjąć tylko wskaźnik w RAM i stąd całe zamieszanie dlaczego skoro jednak trafia do RAM to jednak nie można go tam "poprzestawiać"

    A to że właściwie nie ma sensownego powodu aby umieszczać je w funkcji jako lokalne to tak zdaję sobie sprawę - zaciekawiło mnie tylko to co wyżej - dlaczego nie można go zmienić skoro jednak w tym przypadku pojawił się na 100% w RAM .... no bo chyba kompilator nie zmienił mojej funkcji żeby poradziła sobie z pobraniem wskaźnika z FLASH. Do tego mam

    lcd_str_P()

    tak więc to bardziej takie pytanie teoretyczne - bo jeszcze raz powiem, że jeśli już pojawił się w RAM to na etapie skryptów startowych też musiał być znany po skopiowaniu z FLASH.
  • #8 8800806
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2203
    Nie pojawił się w RAM, po prostu masz tam śmieci po wcześniejszej wersji :) Przejrzyj plik map i lss, zapewniam cię, że nie znajdziesz tam śladu po swoim literale.
    albertb - zdecyduj się, bo raz twierdzisz, że program musi działać, a potem sobie jakby zaprzeczasz. Semantycznie oczywiście nie ma lokalnych literałów, ale można je zdefiniować tak, żeby miały zasięg lokalny.
  • #9 8800914
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    mirekk36 napisał:
    Dlatego już zacząłem grzebać żeby użyć malloc() ... ale teraz widzę, że i takie automatyczne przydzielanie pamięci w małych AVRkach to troszkę przerost formy nad treścią , chyba żeby pisać coś w C++ to może.

    Jeśli chcesz coś takiego robić, to po prostu nie posługujesz się wskaźnikami, tylko tablicą:

    char *ptr = "ten napis jest const - nie wolno go modyfikowac!";
    char array[] = "ten napis nie jest const - mozna go modyfikowac, ale nie mozna go przedluzyc!";


    4\/3!!
  • #10 8800950
    Konto nie istnieje
    Konto nie istnieje  
  • REKLAMA
  • #11 8801230
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Freddie Chopin napisał:
    Jeśli chcesz coś takiego robić, to po prostu nie posługujesz się wskaźnikami, tylko tablicą:[/code]


    Tak wiem wiem że w można się posłużyć tablicą, nurtowało mnie to dlaczego nie mogę zmienić tej zawartości w RAM a na 1000% pojawia się ten "napis" w RAM.

    Teraz już wiem, że na pewno też nie mogę go zmienić pomimo że jest w RAM ;) GCC na to nie POZWOLI za chiny, a na dowód tego że "napis" brany jest z RAM kod w asm:

    
      8a:	0e 94 33 01 	call	0x266	; 0x266 <lcd_cls>
      8e:	80 e6       	ldi	r24, 0x60	; 96
      90:	90 e0       	ldi	r25, 0x00	; 0
      92:	0e 94 6e 01 	call	0x2dc	; 0x2dc <lcd_str>


    co ciekawe właśnie dla mnie to GCC zupełnie ignoruje w takim przypadku polecenie:



    nie ma go w asemblrze ;) podczas gdy "napis" definiowany jest przed funkcją main() to polecenie brane jest pod uwagę i mogę zmienić pierwszy znak.

    Tak więc macie rację , że tak zdefiniowanego tekstu w funkcji nie da się modyfikować.

    I jak mówię nie chodzi mi o to jak można to zrobić tylko właśnie chodziło mi o wyjaśnienie dlaczego w takim przypadku nie zadziała. Okazało się zatem że żadnego głupiego błędu nie popełniłem tylko o czymś po prostu nie wiedziałem jeszcze.

    No ale pozostaje pytanie skąd bierze się w pamięci RAM.

    tmf napisał:
    Nie pojawił się w RAM, po prostu masz tam śmieci po wcześniejszej wersji :) Przejrzyj plik map i lss, zapewniam cię, że nie znajdziesz tam śladu po swoim literale..


    Na pewno nie bierze się ze śmieci po wcześniejszej wersji - tego już jestem pewien i przyznaję rację że w pliku map i lss nie ma po nim śladu ;) więc teraz zagadka dlaczego jak widać w asemblerze powyżej do funkcji lcd_str() został przekazany adres w pamięci RAM 0x60 (przecież to jej początek bo nie mam zdefiniowanych żadnych innych zmiennych) .... i z tejże lokalizacji leci na LCD ładnie "napis" , który nie daje się modyfikować bo GCC nie pozwala i może słusznie - tylko jeszcze nie do końca w tym konkretnym przypadku rozumiem dlaczego ;)


    I powiem więcej - zrobiłem coś takiego (wbrew oczywiście zdrowemu rozsądkowi) i udało mi się zmienić jednak tenże łańcuch w pamięci RAM o tak:

    int main(void) { 
    
      char *str = "napis"; 
    
    	void *z;
    	z = (char*)0x60;
    	*(char*)z='p';
    
    
       lcd_str(str); // teraz na LCD pojawił się zgodnie z oczekiwaniem "papis" ;)
    }

    Oczywiście zdaję sobie sprawę, że to jest bez sensu bo przecież tylko na podstawie pliku *.lss wiem że "napis" siedzi pod adresem 0x60 w RAM , chociaż można by go było odszukać - no ale nie w tym rzecz bo byłoby to brnięciem w ślepą uliczkę.

    Reasumując - pytanie - gdzie? na jakim etapie kompilator przenosi "napis" do RAM? ;)

    Dodano po 1 [minuty]:

    albertb napisał:

    A do mirka: Zadeklaruj poprawnie tablicę jako zmienną i wtedy obojętnie czy ona i wskaźnik będą lokalne czy globalne i w jakiej konfiguracji program będzie działał zgodnie z oczekiwaniami.


    Tak jak już pisałem wcześniej - oczywiście, że zdaję sobie sprawę (teraz nawet lepiej) jak można zrobić żeby to działało - ale mi tu chodziło o wyjaśnienie jak działa kompilator AVR GCC w tak specyficznej sytuacji niż o to jak sobie poradzić żeby to działało.
  • Pomocny post
    #12 8801355
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Kompilator taki napis powinien umieścić tam gdzie inne stałe, czyli w teorii w pamięci flash. Gdyby to był ARM, to tak by się stało, jednak skoro jest to AVR to... jest problem, bo GCC (tak jak i sam język C) został zaprojektowany na architekturę von Neumana (jednorodna przestrzeń adresowa), a tymczasem AVR jest układem w architekturze Harwardzkiej. Z tego względu przypuszczam, że avr-gcc przenosi takie stałe do RAM, bo jest to najprostsze (zapewne też najlepsze) rozwiązanie tego problemu...

    Teraz to sprawdziłem i - tak jak myślałem - w skryptach linkera dla AVR dane stałe (.rodata - read-only data) są po prostu umieszczone ręcznie w RAM:

     .data	  : AT (ADDR (.text) + SIZEOF (.text))
      {
         PROVIDE (__data_start = .) ;
        *(.data)
        *(.data*)
        *(.rodata)  /* We need to include .rodata here if gcc is used */
        *(.rodata*) /* with -fdata-sections.  */
        *(.gnu.linkonce.d*)
        . = ALIGN(2);
         _edata = . ;
         PROVIDE (__data_end = .) ;
      }  > data 


    4\/3!!
  • #13 8801380
    gaskoin
    Poziom 38  
    Posty: 4159
    Pomógł: 436
    Ocena: 102
    Dzieje się to w startupie (dla zmiennych globalnych). I nie robi tego kompilator tylko linker :)
  • Pomocny post
    #14 8801408
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Napis jest przenoszony do ramu w sekcjach .init.

    Nie jestem do końca pewien ale to powinno działać, mam wrażenie że to jest wina optymalizacji.

    Spróbuj:
    
    volatile char * volatile str = "napis"; 

    Ostrzeżenia będa ale czy to zadziała.
  • Pomocny post
    #15 8801434
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2203
    Błędnie zakładasz, że coś z tym wspólnego ma gcc, podczas kiedy odpowiedzi powinieneś szukać w standardzie języka C. Literały są stałymi i jako takie nie mogą być modyfikowane. To, że udało ci się literał zmodyfikować niecnymi sztuczkami to kwestia konkretnej implementacji, podobnie pośrednio za pomocą wskaźników możesz zmodyfikować stałe const - tyle, że całkiem niewykluczone, że na jakiejś platformie wywali to cały program z powodu błędu ochrony pamięci, a w każdym przypadku jest niezgodne ze standardem języka.
    BTW, u mnie kod:
    
    void lcd_str(char *txt)
    {
    	*txt=12;
    }
    
    int main(void) {
    
    char *str = "napis";
    
      *str = 'p';
    
       lcd_str(str);
    }
    

    na podglądzie w symulatorze wyraźnie pokazuje, że literał "napis" nie znajduje się w pamięci RAM - ba, w ogóle jest on wywalany z programu, bo nie ma go także w pamięci FLASH (przy -Os), dopiero wyłączenie optymalizacji (-O0) powoduje, że jest on kopiowany do pamięci RAM. Dopiero definicja lokalnie:
    static char str1[] = "test";
    powoduje zgodnie z oczekiwaniem, że literał kopiowany jest do pamięci RAM.

    Dodano po 15 [minuty]:

    Z punktu widzenia semantyki języka C operacja zmiany literału jest niedozwolona i kompilator może zrobić cokolwiek Dobrze to widać na przykładzie gcc, gdzie zachowanie jest skrajnie różne w zależności od np. optymalizacji. Poza tym wyjątkowo marnym pomysłem jest także tworzenie explicite wskaźników na literały. Z prostej przyczyny - jak odróżnić taki wskaźnik od wskaźnika wskazującego na pamięć zablokowaną dynamicznie. Powodzenia życzę w sytuacji przypadkowego wywołania free str :) C oferuje sensowny mechanizm przekazywania zmiennych łańcuchowych przez wskazanie implicite, co pozwala uniknąć wielu błędów, m.in. zwalniania nieprzydzielonej pamięci.

    Dodano po 3 [minuty]:

    A, jeszcze jeden problem mi wpadł do głowy. Co jeśli gdzieś w programie masz w innych miejscach zdefiniowane identyczne literały "napis"? kompilator zgodnie z zasadami może je połączyć i umieścić w pamięci tylko jedną kopię. Wtedy próbując ją zmodyfikować modyfikujesz wszystkie odwołania do tego literału (co prawda akurat merge-constants na AVR z powodu braku supportu linkera nie działa, ale na ARM i x86 działa wyśmienicie i powyższy problem wystąpi).
  • #16 8801854
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Ok, nie wiem już ile razy mam powtarzać, że to do niczego mi nie potrzebne - po prostu w pewnym momencie nie znając dokładnie tych zasad utworzyłem wskaźnik na literał .... i spotkałem się z dziwnym (ale dla mnie zachowaniem)

    Teraz dopiero z tej dyskusji wyniknęło skąd się to bierze i chyba każdy z was także niechcąco odpowiadając mi sam się dowiedział dlaczego tak a nie inaczej.

    tmf myślał że to pozostałości , "śmieci" w RAM, inni że to powinno zadziałać itf.

    Tymczasem oczywiście racja jest po waszej stronie, że jeśli znać zasady - tzn - po prostu nie przyszło mi do głowy, że w takiej definicji:

    char *str = "napis";


    że to wskaźnik niejako na stałą dosłowną (literał) i oczywiście przeczy to zasadom standardu języka C .... aczkolwiek widać, że właśnie w tej implementacji na AVR zachowuje się to troszkę inaczej tak "w środku"

    I nie mam zamiaru nigdy tak robić - bo już pisałem że wiem iż to jest bez sensu - ale za to wyjaśniło mi się teraz to do końca ;)

    Dodano po 4 [minuty]:

    michalko12 napisał:
    Napis jest przenoszony do ramu w sekcjach .init.

    Nie jestem do końca pewien ale to powinno działać, mam wrażenie że to jest wina optymalizacji.

    Spróbuj:
    
    volatile char * volatile str = "napis"; 

    Ostrzeżenia będa ale czy to zadziała.


    Tak oczywiście że to działa i bez ostrzeżeń jeśli się zrobi rzutowanie przy przekazaniu argumentu

    lcd_str( (char*)str);


    sprawdziłem ;) .... ale jak mówię - sprawdziłem tylko dla IDEI - a nie żeby to wykorzystywać w praktyce.
  • #17 8802127
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2203
    Ja nigdzie nie twierdzę, że chcesz to do czegoś wykorzystać. Po prostu opisałem kod, który pokazałeś i problemy jakie się z nim wiążą.
    Co do definicji z modyfikatorem volatile - IMHO to kiepskawe rozwiązanie. volatile efektywnie w gcc działa tak jakby lokalnie wyłączyć optymalizację, ale w tym przypadku to, że pozostawia literał w RAM to przypadek, a nie coś co wynika z semantyki języka. Stąd IMHO lepiej zastosować konstrukcję typu static char *, powodującą, że odpowiedni wskaźnik (a co za tym idzie kopia literału) tworzona jest przez skrypty startowe programu i całość zachowuje się tak jakby była zadeklarowana globalnie, ale scope mamy lokalny - czyli dokładnie to co chcieliśmy osiągnąć, w dodatku jak najbardziej zgodnie ze standardem języka. Oczywiście takiego literału ciągle nie możemy modyfikować. Ale już w rozwiązaniu jakie podał Freddie Chopin z char str[]="xxx"; oczywiście problemu z modyfikacją nie ma - jest to zwykła zmienna.
  • #18 8802196
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    tmf napisał:
    Co do definicji z modyfikatorem volatile - IMHO to kiepskawe rozwiązanie. volatile efektywnie w gcc działa tak jakby lokalnie wyłączyć optymalizację.


    Dokładnie się z tym zgadzam, dlatego ten przykład to sprawdzenia to i tak była tylko taka sztuka dla sztuki.

    dlatego uważam, że w zasadzie problem został rozwiązany do samego koniuszka.
  • #19 8802253
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    tmf napisał:
    Co do definicji z modyfikatorem volatile - IMHO to kiepskawe rozwiązanie. volatile efektywnie w gcc działa tak jakby lokalnie wyłączyć optymalizację, ale w tym przypadku to, że pozostawia literał w RAM to przypadek, a nie coś co wynika z semantyki języka.


    Mirekk36 chciał sie dowiedziec dlaczego tak działa a tak nie, dlatego podszedłem do tego na logikę. Skoro działało poza main nie było powodów żeby nie działało lokalnie.
    Stawiałem na optymalizację. Pod ARMem ze względu na architekturę i tak by ten przypadek nie przeszedł ponieważ stałe nie są kopiowane do RAMu.

Podsumowanie tematu

✨ Dyskusja dotyczy problemu z modyfikacją literałów w języku C na architekturze AVR. Użytkownik zauważył, że podczas próby zmiany zawartości wskaźnika na literał w funkcji main, operacja nie działa, podczas gdy w przypadku wskaźnika globalnego działa poprawnie. Uczestnicy rozmowy wyjaśniają, że literały są stałymi i nie mogą być modyfikowane, a ich lokalne wskaźniki mogą wskazywać na pamięć FLASH, co prowadzi do błędów. Zasugerowano użycie tablicy zamiast wskaźnika na literał oraz omówiono różnice w alokacji pamięci między zmiennymi globalnymi a lokalnymi. Wskazano również na różnice w zachowaniu kompilatora GCC w kontekście architektury von Neumana i Harwardzkiej.
Wygenerowane przez model językowy.
REKLAMA