Google+ Start   |   E-mail   |   Newsletter:

domyślna promocja do typu int

Wiele osób często spotyka się z problemem związanym z niezrozumieniem zagadnienia jakim jest "domyślna promocja do typu int w AVR GCC". Najczęściej zgłaszany przypadek pochodzi z rozdziałów książki na temat przetwornika ADC oraz obliczeń, które tam są wykonywane. Rzeczywiście w książce zabrakło uzupełnienia wiedzy w tym zakresie, i stąd takie nieporozumienia, dlatego teraz chciałbym to wyjaśnić. Chodzi mianowicie o przykładowy kod ze strony nr 244:


 
      // definicje zmiennych

      uint32_t wynik;
      uint16_t pm;
      uint8_t cz_d;
      uint16_t cz_u;
     
      pm = pomiar(5); // odczytana wartość ADC np. = 850
      wynik = pm * 1 * 2957; // wynik = 2.513.450
      cz_d = wynik/100000; // część dziesiętna = 25

 


Zgłaszany przez czytelników problem polega na tym, że w wyniku obliczenia zmienna wynik najczęściej zawiera nieprawidłowe dane, przez co wyniki na ekranie LCD w omawianym przykładzie nie odwzorowują poprawnie mierzonych napięć w całym zakresie pomiarowym. Proszę zwrócić uwagę, że napisałem iż zgłoszenia mówią o tym, że czasem wynik jest prawidłowy a czasem nie. Spróbujmy razem dojść gdzie leży przyczyna tego zjawiska i jak sobie poradzić. Tu z pomocą przychodzi nam drobna informacja, której być może zabrakło w książce, chodzi mianowicie o to, że kompilator domyślnie dokonuje tzw "promocji do typu int" każdego obliczanego wyrażenia, jeśli programista w jawny sposób nie określi że miałoby być inaczej. Cóż oznacza to wydawać by się mogło tajemnicze określenie promocja. Otóż kompilator zakłada, że z punktu widzenia zachowania najlepszej optymalizacji swoich działań, domyślnie obliczy każde wyrażenie w taki sposób, że jego wynik będzie się mieścił w zakresie liczby typu int. Skoro tak, to wiemy doskonale, że typ int potrzebuje do swojej reprezentacji 2 bajtów ! Nie więcej. Cóż z tego wynika ?

Rozpatrzmy pierwszy przykład:
 

 

      uint8_t liczba1 = 6;
      uint8_t wynik;
     
      wynik = liczba1 * 6;


Zwykle nie każdy na początku znajomości języka AVR GCC zdaje sobie sprawę, że wyrażenie liczba1 * 6 zostanie obliczone za naszymi plecami i umieszczone w przejściowej zmiennej o rozmiarze dwóch bajtów, a nie jednego jakby wydawać się mogło skoro zarówno zmienna "liczba1" jak również zmienna "wynik" są typu uint8_t czyli zajmują tylko jeden bajt. Jak to więc się dzieje, że na końcu otrzymujemy prawidłową wartość w zmiennej wynik, którą np wyświetlimy sobie na alfanumerycznym wyświetlaczu LCD ? Otóż kompilator dokona tutaj za nas "niejawnego rzutowania" wyniku int, 16-bitowego (2-bajtowego) , do typu takiego jaki dostrzegł przy zmiennej wynik. Zdefiniowaliśmy ją bowiem jako typ uint8_t (8-bitowy). Można powiedzieć, że to rzutowanie niejawne będzie wyglądało w ten sposób:

 


      wynik = (uint8_t)(liczba1 * 6);


Dokładnie w ten sam sposób moglibyśmy dokonać jawnego rzutowania w kodzie programu i tak napisać jak powyżej. Będzie to prawidłowy zapis. W takim razie przejdźmy do kolejnego przykładu, tym razem na dużo większych liczbach:

 


     
uint16_t liczba1 = 8;
      uint32_t wynik;
     
      wynik = liczba1 * 9000;



Tym razem zaczynamy mieć już do czynienia z sytuacją podobną jak w przypadku przedstawionym na początku, pochodzącym z książki. Jak widać zmienna liczba1 teraz jest typu 16-bitowego, natomiast zmienna wynik musi być przecież większego typu ponieważ jak widać jeśli pomnożymy 8*9000 to z pewnością przekroczymy zakres 16-bitowy (mieszczący wartości od 0 do 65535), ponieważ wyrażenie to będzie = 72000. Widząc takie przygotowania i dobranie typów, a nie wiedząc o promocji obliczania wyrażeń do typu 16-bitowego, natrafimy na błąd i konsternację. Zobaczymy bowiem na wyświetlaczu LCD liczbę 6464 ! zamiast spodziewanej 72000. Zachęcam do wykonania takiego ćwiczenia we własnym zakresie aby lepiej to zrozumieć. Spójrzmy jednak na binarną reprezentację liczby 72000:

 


      0b00000000 00000001
00011001 01000000


Proszę spojrzeć, teraz już wszystko się powinno wyjaśnić, promocja do typu int spowodowała (16-bitowego), że do zmiennej wynik trafiło tylko najmłodsze słowo liczby 72000, zaprezentowane w kolorze czerwonym. Wartość tych dwóch bajtów 00011001 01000000 to dokładnie liczba 6464 ! Te dwa najstarsze bajty nie zostały w ogóle wzięte pod uwagę. Zanim jednak przejdę do wyjaśnienia jak sobie radzić w takich przypadkach podpowiem tylko co powoduje błąd w przykładzie z książki, przedstawionym na samym początku. Otóż jeśli w wyrażeniu:


      wynik = pm * 1 * 2957;


wartość zmiennej pm (czyli odczytajen wartości naszego ADC) nie będzie większa niż 22, to wyniki pomiaru nie będą zakłócone, dlatego że:


      wynik = 22 * 1 * 2957;


wartość wyrażenia będzie równa 65054 a zatem mieści się on w zakresie 16-bitowej wartości - zgadza się? Zgadza, spójrzmy co się może stać jeśli wartość ADC (zmienna pm) zwiększy się chociażby o 1:


      wynik = 23 * 1 * 2957;


Niestety tym razem wartość wyrażenia będzie równa 68011 ! a zatem przekroczy maksymalną wartość jaka może być przechowywana w zmiennej 16-bitowej. Zatem każdy większy odczyt z ADC niż 22 będzie w wyniku takiego obliczenia błędnie wyświetlany, i już wiemy dlaczego. Z tego samego powodu jak to było z liczbą 72000 w przykładzie omawianym wyżej.

Przejdźmy zatem do sedna sprawy, jak sobie radzić w takich sytuacjach, czy może jesteśmy bez wyjścia ? Oczywiście, że nie jesteśmy bez wyjścia. Rozwiązań jest kilka a jednym z nich jest to na które wpadają zwykle sami czytelnicy spotykając ten błąd. Niestety jest to wyjście (w tym konkretnym przypadku) zbyt kosztowne jeśli chodzi o gospodarkę pamięcią RAM, stracimy bowiem 2 dodatkowe bajty. Zastosowanie go polecam już pod własną rozwagę, w porównaniu z następnym, które za chwilę przedstawię. Wystarczy sobie odpowiedzieć dlaczego tutaj kompilator zastosował promocję do typu 16-bitowego obliczając takie wyrażenie ? Zauważył bowiem (kolokwialnie mówiąc), że zmienne i stałe biorące udział w wyrażeniu posiadają typy nie większe niż 16-bit. I uznał za stosowne dokonać promocji obliczenia wyniku do domyślnego typu 16-bitowego. W związku z powyższym jednym ze sposobów aby zmusić kompilator do zaniechania takiej promocji może być użycie chociaż jednej zmiennej większego typu w całym wyrażeniu, a już wynik tego wyrażenia zostanie umieszczony w takim typie jaki posiada największa zmienna. To znaczy ta zmienna która posiada najbardziej pojemny typ. A zatem pierwszy kosztowny sposób to zdefiniowanie w naszym głównym przykładzie zmiennej o nazwie pm, jako typ 32-bitowy:


     
// definicje zmiennych
      uint32_t wynik;
      uint32_t pm;    // tu zaszła zmiana
      uint8_t cz_d;
      uint16_t cz_u;
     
      pm = pomiar(5); // odczytana wartość ADC np. = 850
      wynik = pm * 1 * 2957; // wynik = 2.513.450


Proszę bardzo teraz wyrażenie (pm * 1 * 2957) zostanie prawidłowo przekazane do zmiennej wynik, czyli już jako pełna 32-bitowa liczba. Niestety każdy widzi, że kosztowało nas to 2 cenne dodatkowe bajty pamięci RAM. Można sobie poradzić jeszcze w inny sposób, nie tracąc tych dwóch bajtów pamięci, oto kolejny przykład z poprawką, zastosujemy tutaj jawne rzutowanie typu dla zmiennej pm, która ma brać udział w wyrażeniu. Jednym słowem powiemy kompilatorowi stanowczym tonem, że ma zaniechać promocji do int:


     
// definicje zmiennych

      uint32_t wynik;
      uint16_t pm;
      uint8_t cz_d;
      uint16_t cz_u;
     
      pm = pomiar(5); // odczytana wartość ADC np. = 850
      wynik = (uint32_t)pm * 1 * 2957; // tu zaszła zmiana !!!

 

Dzięki takiej operacji, przyznasz pewnie, że prostej uzyskujemy już prawidłowe wyniki dla każdej wartości ADC. Ponieważ jednak w tym wyrażeniu biorą jeszcze udział "stałe dosłowne", mam tu na myśli liczby 1 oraz 2957, możemy użyć jeszcze innego - także bardzo często spotykanego sposobu. Niestety tu podobnie kompilator traktuje domyślnie stałe jako zmienne 16-bitowe (taka myśl powinna ci się już nasunąć wyżej), skoro bez jawnego rzutowania do typu uint32_t nie mogliśmy osiągnąć prawidłowych wyników. Wspominałem jednak w książce (dokładnie na stronie nr 85, rozdział "4.3.2.1 Stałe jako liczby całkowite", o tym fakcie, w jaki sposób można komunikować kompilatorowi aby odpowiednio traktował nasze liczby. Możemy się bowiem posłużyć literami na końcu stałych, które określą znowu jawnie ich typ jaki chcemy uzyskać. A zatem ostatni przykład i sposób jaki polecam do rozwiązania tego konkretnego problemu.


     
// definicje zmiennych

      uint32_t wynik;
      uint16_t pm;
      uint8_t cz_d;
      uint16_t cz_u;
     
      pm = pomiar(5); // odczytana wartość ADC np. = 850
      wynik = pm * 1 * 2957UL; // tu zaszła zmiana !!!

Jak widzisz dodałem oznaczenie UL (Unsigned Long) co oznacza dokładnie typ 32 bitowy, proszę zajrzeć sobie do tabeli typów w książce. Naturalnie te dwie literki można dodać do dowolnej liczby w tym wyrażeniu czyli nawet do tej jedynki. Można także do każdej. Ale z powyższych wyjaśnień pewnie już rozumiesz, że dodawanie do każdej nie jest konieczne ponieważ wystarczy aby jedna składowa wyrażenia posiadały typ większy od domyślnego 16-bitowego i od razu kompilator zrezygnuje z domyślnej promocji do int.

 

Na tym kończę i mam nadzieję, że artykuł ten z powodzeniem wyczerpuje nie tylko rozwiązanie problemu z książki ale także przynosi dodatkową wiedzę jak radzić sobie z obliczeniami na większych liczbach w języku AVR GCC. Wbrew pozorom jest do bardzo istotne zagadnienie.

W związku z tym, iż na naszym forum technicznym pojawiły się ciekawe pytania dotyczące bezpośrednio tego tematu oraz padły dodatkowe wyjaśnienia, to przy tej okazji gorąco polecam zapoznać się także z wątkiem z linku poniżej:

http://forum.atnel.pl/topic1595.html

ikona Strona główna ikona O nas ikona Wydawnictwo ikona Elektronika ikona Oprogramowanie ikona Kursy ATNEL ikona Nowości ikona SKLEP ikona FORUM ikona Kontakt ikona Polityka Prywatności Cookie

ATNEL Nowoczesne Rozwiązania - programowanie AVR w C | pisanie programów dla AVR | pisanie programów ATmega | pisanie programów dla AVR | programowanie mikrokontrolerów |
mikrokontrolery AVR programowanie | programowanie w C mikrokontrolerów | programowanie ATmega | programy w C AVR
Realizacja: Dpl Agency - Projektowanie Stron Internetowych