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] [C][AVR] - strtok_r i wskaźnik do wskaźnika: różnice w zachowaniu funkcji

mirekk36 29 Lis 2010 21:21 2772 17
REKLAMA
  • #1 8806240
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Witam,

    Posługuję się z lubością funkcjami wbudowanymi typu:

    strtok()
    strtok_r()

    Chodzi mi jednak o tę drugą, której to podejrzane jak dla mnie zachowanie zaobserwowałem w pewnym przypadku. Zdaję sobie sprawę, że to moja wina ale może ktoś pomoże mi się naprowadzić na właściwe tory gdzie źle myślę albo robię błąd. W ksrócie problem opisałbym tak:

    gdy mam kod:

    char rsbuf[250];
    char *reszta;
    
    int main(void) {
    
       // inicjalizacja itp
    
       char *wsk;
       while(1) {
           wsk = strtok_r(rsbuf, "*", &reszta);
      
           // coś tam się dzieje dalej
       }
    
    
    }


    nie mam żadnych dziwnych dla mnie objawów, strtok_r() działa OK!

    ale gdy zdefiniuję wskaźnik *reszta wewnątrz main() czyli niejako na stosie (bo to w obszarze zmiennych automatycznych przecież)
    char rsbuf[250];
    
    int main(void) {
    
       // inicjalizacja itp
    
       char *reszta;
       char *wsk;
    
       while(1) {
           wsk = strtok_r(rsbuf, "*", &reszta);
      
           // coś tam się dzieje dalej
       }
    
    }


    no to już mam niewesoło - tłumaczę jaki efekt. Okazuje się, że tym razem funkcja strtok_r() już nigdy nie wykryje mi separatora "*" (tak jakby w ogóle go nie było a wskaźnik *wsk wskazuje na cały łańcuch wraz z gwiazdką * - nie została wykryta.

    A teraz ciekawostka, mogę to naprawić umieszczając *reszta za pomocą specyfikatora static w obszarze zmiennych globalnych. Czyli tak jak niżej już działa poprawnie:

    char rsbuf[250];
    
    int main(void) {
    
       // inicjalizacja itp
    
       static char *reszta;
       char *wsk;
    
       while(1) {
           wsk = strtok_r(rsbuf, "*", &reszta);
      
           // coś tam się dzieje dalej
       }
    
    }


    Uwaga! takie zjawisko nie występuje mi gdy identycznie postępuję w każdej funkcji - z tą różnicą , że tam nie działa to w pętli while(1)

    Wiem oczywiście, że pierwsze wywołanie strtok_r() musi być ze wskaźnikiem na łańcuch a następne wywołania już wymagają NULL w tym miejscu. Ale skoro na początku pętli while(1) resetuję bufor, znowu coś do niego odbieram i chciałbym go od nowa analizować to hmmm chyba mogę podać znowu nazwę rsbuf (bufora) a nie NULL. Zresztą pokazałem przypadki gdzie to działa - a nie działa tylko w tej jednej sytuacji gdy wskaźnik *reszta nie jest statyczny. Tzn może źle mówię - nie źle działa - tylko budzi moje zdziwienie takie zachowanie i być może czegoś nie wiem jeszcze o tej funkcji albo .... o zachowaniu się stosu przy definiowaniu w pętli main zmiennych automatycznych - to musi mieć coś z tym wspólnego tak mi się wydaje.

    Reasumując - wiem jak sobie radzić - ale jak zwykle nie daje mi spokoju dlaczego to w opisanej konkretnej sytuacji zawsze nie działa.
  • REKLAMA
  • #2 8806678
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Prawdopodobnie znowu optymalizacja jest przyczyną.
    Zmienna lokalna char *reszta; jest po optymalizacji w rejestrze, a do rejestru nie mozna wyłuskac adresu. Spróbuj znowu z volatile

    char * volatile reszta;
  • #3 8806722
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Oczywiście rączo pośpieszyłem sprawdzić ;) ... ale nie, to nie to. Zresztą dziwi mnie troszkę związek z tym samego definiowana wskaźnika *reszta. Przecież problemem wydaje się być jakby znalezienie samego separatora.
  • #4 8806737
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    michalko12 napisał:
    Prawdopodobnie znowu optymalizacja jest przyczyną.
    Zmienna lokalna char *reszta; jest po optymalizacji w rejestrze, a do rejestru nie mozna wyłuskac adresu.

    Żaden kompilator nie umieści takiej zmiennej w rejestrze, skoro gdzieśtam potrzebny jest jej adres. Tak samo funkcje statyczne nigdy nie zostaną za-inline-owane jeśli gdzieś używane są ich adresy.

    IMHO tu może być problem z czymś innym, a że wyskakuje w takiej sytuacji to tylko przypadek. Trzeba podejrzeć assemblera, ale tego AVRowego zbytnio nie znam, autor wątku pewnie zna go 100x lepiej odemnie, więc tam polecałbym zajrzeć.

    4\/3!!
  • #5 8806824
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    Żaden kompilator nie umieści takiej zmiennej w rejestrze, skoro gdzieśtam potrzebny jest jej adres. Tak samo funkcje statyczne nigdy nie zostaną za-inline-owane jeśli gdzieś używane są ich adresy.

    Zdaje sobie z tego sprawę, że nie powinien tego robić, ale kompilator to tylko program. Bardzo podobną sytuację mam z FreeRTOSem, jesli podam adres zmiennej lokanej do pewnej funkcji FreeRTOSa w przerwaniu to cały program idzie w maliny, static pomaga.


    Mirek, spróbuj char *wsk; umieścić wewnątrz pętli while(1). Z punktu widzenia C nie powinno mieć to znaczenia, ale kompilatory nigdy nie beda do końca idealne.
  • REKLAMA
  • #6 8806846
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    No tak rzeczywiście zanim to napisałeś to rzuciłem okiem na asm, co się okazuje hmm:

    zarówno gdy jest volatile czy bez volatile to wskaźnik umieszczany jest w parze rejestrów r31:r30 - ale z tego co widzę to do tych rejestrów ładowana jest chyba jakaś pozycja zlokalizowana w pamięci RAM tyle że na stosie - wskazywana przez rejestr Y

    
         eec:	e9 81       	ldd	r30, Y+1	; 0x01
         eee:	fa 81       	ldd	r31, Y+2	; 0x02
         ef0:	8a e7       	ldi	r24, 0x7A	; 122
         ef2:	80 83       	st	Z, r24


    dopiero gdy użyję static, to do tejże samej pary rejestrów ładowana jest konkretna komórka pamięci z obszaru zmiennych globalnych:

    
         eec:	e0 91 1c 01 	lds	r30, 0x011C
         ef0:	f0 91 1d 01 	lds	r31, 0x011D
         ef4:	8a e7       	ldi	r24, 0x7A	; 122
         ef6:	80 83       	st	Z, r24


    czyli teoretycznie powinno działać i w jednym i w drugim przypadku, bo przecież po wejściu w main, tworzone jest na stosie miejsce na ten wskaźnik i zdaje się do tej lokalizacji odwołują się później rejestry ... a przecież do funkcji strtok_r przekazywany jest adres tego wskaźnika ... zaraz spróbuję zobaczyć dalej jak to wygląda przed wywołaniem samej funkcji strtok_r()
  • #7 8806861
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    michalko12 napisał:
    Zdaje sobie z tego sprawę, że nie powinien tego robić, ale kompilator to tylko program. Bardzo podobną sytuację mam z FreeRTOSem, jesli podam adres zmiennej lokanej do pewnej funkcji FreeRTOSa w przerwaniu to cały program idzie w maliny, static pomaga.

    Nie znam całej sytuacji, ale zmienne lokalne niekoniecznie istnieją gdy zostanie wywołane przerwanie, które chciałoby ich użyć. To akurat nie byłaby wina kompilatora.

    Ponownie mówię - 99,666% problemów NIE jest winą kompilatora.

    4\/3!!
  • #8 8806984
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    michalko12 --> próbowałem umieścić w pętli while(1), też nic nie daje niestety, ale w końcu zobaczyłem że coś dziwnego dzieje się w asemblerze, otóż gdy zastosowany jest static dla *reszta to wtedy wywołanie funkcji strtok_r() zrobione jest tak:

    
         f80:	cf 01       	movw	r24, r30
         f82:	60 e0       	ldi	r22, 0x00	; 0
         f84:	71 e0       	ldi	r23, 0x01	; 1
         f86:	4c e1       	ldi	r20, 0x1C	; 28
         f88:	51 e0       	ldi	r21, 0x01	; 1
         f8a:	0e 94 51 19 	call	0x32a2	; 0x32a2 <strtok_r>


    tutaj widać wyraźnie (bo sprawdziłem wcześniej - wyżej) że w parze rejestrów r20 i r21 przekazywany jest właśnie adres 0x011C komórki pamięci czyli adres wskaźnika &reszta i to działa prawidłowo,

    ale gdy nie dam static albo tylko volatile to już dzieje się coś takiego:

    
         f78:	cf 01       	movw	r24, r30
         f7a:	60 e0       	ldi	r22, 0x00	; 0
         f7c:	71 e0       	ldi	r23, 0x01	; 1
         f7e:	ae 01       	movw	r20, r28
         f80:	4f 5f       	subi	r20, 0xFF	; 255
         f82:	5f 4f       	sbci	r21, 0xFF	; 255
         f84:	0e 94 4f 19 	call	0x329e	; 0x329e <strtok_r>


    tym razem te 3 linie:

    movw r20, r28
    subi r20, 0xFF	
    sbci r21, 0xFF	


    wyglądają jakby to była próba załadowania adresu wskaźnika leżącego na stosie - bo r29:r28 to rejestr Y i chyba on trzyma wskaźnik stosu ??? ale może to się jakoś nie udaje - może wcześniej "po drodze" do tego rozkazu coś przejeżdża po tym stosie ? ...
  • #9 8807024
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Taki przykład:

    void ISR(void)
    {
    	
    	static portBASE_TYPE xHigherPriorityTaskWoken;
    
    	SemaphoreGiveFromISR(ModemParserSemaphore,&xHigherPriorityTaskWoken);
    	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
    


    xHigherPriorityTaskWoken bez static i program płynie. Nawet przykłady ze strony FreeRTOSa mają static. Teraz mówię sobie "OK, tak musi być, jakis powód tego jest" ale ja jeszcze tego powodu nie znam i tez bym chciał się dowiedziec o co chodzi.
  • #10 8807046
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    No ale w przerwaniu to mi się wydaje (o ile dobrze rozumiem), że bez static twoja zmienna zostaje za każdym razem utworzona na stosie po wejściu w przerwanie i przy wyjściu jest niszczona , ale ....

    no tak ale tu wydaje się ona być nie potrzebna pomiędzy przerwaniami bo chyba ją za każdym razej napełniasz tą funkcją SemaphoreGiveFromISR() zatem w zasadzie to rzeczywiście chyba sytuacja jakby nieco podobna do tej mojej tu opisanej. No bo mógłby przecież być ładnie podany adres tej zmiennej utworzonej chwilę wcześniej na stosie ....

    hmm no ciekawe
  • REKLAMA
  • #11 8807071
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    mirekk36 napisał:
    no tak ale tu wydaje się ona być nie potrzebna pomiędzy przerwaniami bo chyba ją za każdym razej napełniasz tą funkcją SemaphoreGiveFromISR()


    Dokładnie tak jest. Duzo czasu minęło zanim doszedłem co mi powoduje wyjątki (Cortex).
  • #13 8807124
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    mirekk36 napisał:

    tym razem te 3 linie:
    movw r20, r28
    subi r20, 0xFF	
    sbci r21, 0xFF	



    r20:r21 są trzecim argumentem funkcji , r28:r29 prawdopodobnie przetrzymuja kopię wskaźnika stosu, bo rzeczywisty wskaźnik stosu pomiejszony jest o rozmiar zajmowany przez zmienne lokalne. Pewnie wszystko byłoby ok gdyby nie to że, nie bardzo mu wychodzi obliczenie offsetu do zmiennej. Możliwe, że trafiłeś w te 0.333% ;)
  • #14 8807143
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    No tak, trafiłem nie trafiłem ;) ... widać po prostu trza w takich przypadkach jak i u ciebie dawać to static i już. Bo liczyć na to że w innym przypadku uda mu się obliczyć prawidłowy offset to już by była loteria ;)
  • #15 8807550
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Ja wciąż myślę, że błąd jest zupełnie gdzie indziej. Przytnij lepiej ten przykład maxymalnie i pokaż tego assemblera. No i zobacz na innych poziomach optymalizacji, bo wbrew temu co zwykle piszesz są one BARDZO przydatne.

    Poza tym - po co w ogóle używasz strtok_r() zamiast zwykłego strtok()? Przecież one działają totalnie identycznie, z tą różnicą, że ..._r() można używać "wielowątkowo" (_r = reentrant), co w Twoim przypadku zapewne nie ma miejsca.

    4\/3!!

    4\/3!!
  • #16 8807778
    mirekk36
    Poziom 42  
    Posty: 9195
    Pomógł: 964
    Ocena: 2289
    Freddie Chopin -->

    Tak masz rację posprawdzam to jeszcze szczegółowo na krótszym kodzie bo może coś mi tam bruździ, chociaż z drugiej strony całość poza tym działa idealnie.

    Sprawdzę na innych poziomach optymalizacji , wcale nie piszę wszędzie że mam coś przeciwko innym poziomom optymalizacji. Źle jak to często bywa może interpretujesz moje wypowiedzi. Zwykle mówię, żeby początkujący , który nie wie jeszcze podstawowych rzeczy, używał tylko -Os zamiast mówić że mu ta optymalizacja wszystko psuje i nie kompilują mu się puste pętle (wiesz przecież o jakie posty chodzi). Tylko to miałem zawsze na myśli w/s stopni optymalizacji a nic innego (to tak tytułem wyjaśnienia)

    Dlaczego używam strtok_r() bo w tym konkretnym przypadku potrzebuję wskaźnika na pozostałą część łańcucha do analizy czyli tą moją *reszta, ponieważ przekazuję go jako argument do całkiem innej funkcji do dalszej obróbki (parsowania) , zatem chociażby z tego powodu strtok_r() przydaje się nie tylko w wątkach w dosłownym znaczeniu.

    mam np: kilka wywołań funkcji:

    preamble = strtok_r( buf , ",", &reszta);
    frame_start = strtok_r( NULL, ",", &reszta); 
    cmd = strtok_r( NULL, ",", &reszta); 

    i w pewnym momencie - pozostałą część łańcucha w zależności od polecenia jakie nadleciało wcześniej

    parse_data( reszta );

    żeby w funkcji parse_data() ponownie użyć tak jak wyżej.

    Ze zwykłą funkcją strtok() takie przekazanie pozostałej części łańcucha byłoby troszkę bardziej skomplikowane chyba.
  • Pomocny post
    #17 8807837
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    mirekk36 napisał:
    Dlaczego używam strtok_r() bo w tym konkretnym przypadku potrzebuję wskaźnika na pozostałą część łańcucha do analizy czyli tą moją *reszta, ponieważ przekazuję go jako argument do całkiem innej funkcji do dalszej obróbki (parsowania) , zatem chociażby z tego powodu strtok_r() przydaje się nie tylko w wątkach w dosłownym znaczeniu.

    mam np: kilka wywołań funkcji:

    preamble = strtok_r( buf , ",", &reszta);
    frame_start = strtok_r( NULL, ",", &reszta); 
    cmd = strtok_r( NULL, ",", &reszta); 

    i w pewnym momencie - pozostałą część łańcucha w zależności od polecenia jakie nadleciało wcześniej

    parse_data( reszta );

    żeby w funkcji parse_data() ponownie użyć tak jak wyżej.

    Ze zwykłą funkcją strtok() takie przekazanie pozostałej części łańcucha byłoby troszkę bardziej skomplikowane chyba.

    No ale wtedy po prostu miałbyś parse_data() bez parametrów, a w środku wywołujesz strtok() tak jak wyżej (z NULL i separatorami). Idea wersji ..._r() jest taka, że możesz JEDNOCZEŚNIE rozdzielać kilka różnych ciągów (albo w wątkach, albo gdy masz taką potrzebę). Jeśli faktycznie chcesz dobrać się do reszty łańcucha w zupełnie innym celu, to wystarczy wywołać strtok() jeszcze raz - z pustą listą separatorów - strok(NULL, ""); . Jak wiadomo zawsze na wszystko jest 100 sposobów [;

    Zwyczajne strtok() wygląda z grubsza tak:

    char *strtok(char* string, char* separators)
    {
    static char* r;
    
    return strtok_r(string, separators, &r);
    }


    4\/3!!
  • REKLAMA

Podsumowanie tematu

✨ W dyskusji poruszono problem związany z funkcją strtok_r() w języku C, szczególnie w kontekście użycia wskaźnika do wskaźnika. Użytkownik zauważył różnice w zachowaniu funkcji, gdy wskaźnik *reszta jest zdefiniowany jako zmienna lokalna w funkcji main() w porównaniu do sytuacji, gdy jest zdefiniowany jako static. Uczestnicy dyskusji sugerowali, że problem może wynikać z optymalizacji kompilatora, który może umieszczać zmienne lokalne w rejestrach, co uniemożliwia prawidłowe przekazywanie ich adresów. Wskazano również na znaczenie użycia static w kontekście przerwań oraz na różnice w generowanym kodzie assemblera. Ostatecznie, podkreślono, że strtok_r() jest przydatne w sytuacjach wymagających wielowątkowości lub analizy pozostałej części łańcucha.
Wygenerowane przez model językowy.
REKLAMA