Typy premenných v avr-gcc
Zo stránky SensorWiki
Základné[1] typy premenných v avr-gcc
K dispozícii máme všetky bežné typy, ktoré sa používajú v jazyku C.
Veľkosť Rozsah (signed) char 1 Byte (8 bitov) <−127, +127> unsigned char 1 Byte (8 bitov) <0, 255> signed int 2 Byty (16 bitov) <−32,767, +32,767> unsigned int <0, 65,535> signed long 4 Byty (32 bitov) <−2,147,483,647, +2,147,483,647> unsigned long <0, 4,294,967,295> float 4 Byty (32 bitov) IEEE-754 https://en.wikipedia.org/wiki/Single-precision_floating-point_format double =float[2]
Okrem toho má programátor k dispozíci aj nové typy podľa štandardu C99, ktoré majú pevne definovanú veľkosť a tým pádom je zdrojový kód prenositeľnejší. Tieto typy nájdete v knižnici
<inttypes.h>
a sú dostupné aj cez hlavičkový súbor (header) <stdint.h>
.
int8_t 1 Byte (8 bitov) je to alias pre signed char uint8_t 8-bit unsigned type int16_t 2 Byty (16 bitov) = signed int / 16-bit signed type uint16_t 16-bit unsigned type. int32_t 4 Byty (32 bitov) 32-bit signed type uint32_t 32-bit unsigned type int64_t 8 Bytov (64 bitov) 64-bit signed type[3] uint64_t 64-bit unsigned type
Okrem vyššieuvedených pridáva C99 aj typ bool
definovaný v <stdbool.h>
headri. Okrem toho sú tam aj makrá pre hodnoty true
and false
.
Má zmysel používať tento nový typ, keď aj tak zeberie 1 bajt podobne ako unsigned char? Rozdiel je vidieť v nasledovnom príklade. Používame v ňom aj deklaráciu volatile, aby nám pri pokusoch kompilátor nevyhodnotil tento kód ako nevyužitý a nevyhodil ho celkom z výsledného programu.
#include <stdbool.h>
volatile bool log_x;
volatile char char_x;
main(void)
{
log_x = 255; // obsahuje true
char_x = 255; // obsahuje 255, cize true
log_x = log_x + 1; // stale obsahuje true, pretoze 1+1 = 2 a to je nenulova hodnota, cize true
char_x = char_x + 1; // tu bude 0, cize false, pretoze 255+1=256, hodnota pretecie a dostaneme nulu, cize false
}
Poznámky:
- ↑ Zatiaľ vynechávame polia, smerníky a typy Enum, Union a pod.
- ↑ Anywhere the compiler sees "double", it says to itself, "Aha! The puny human said double. But I know better, so I'll replace it with float. Muwahahaha!"
- ↑ Note: This types are not available when the compiler option -mint8 is in effect.
Aritmetické operácie so základnými typmi
Pozrime sa podrobnejšie, aká je výpočtová náročnosť pre jednoduché aritmetické operácie. Ak by ste si to chceli vyskúšať, je potrebné deklarovať premenné ako volatile, inak kompilátor (vcelku rozumne) usúdi, že premenné, s ktorými nič nerobíme nie sú potrebné a z kódu ich vyhodí. V komentároch na každom riadku uvádzame okrem veľkosti premennej v pamäti aj na koľko inštrukcií sa preloží daný riadok a koľko bude trvať jeho vykonávanie. Nepočítali sme presne počet strojových cyklov, uspokojili sme sa s odčítaním času v simulátore. Hodnoty platia pre kryštál 16 MHz. Veľkosť kódu je len orientačná, tam kde sa volali špecifické podprogramy uvádzame CALL.
// Premenna: Kod: Poznamka:
// Miesto v pamati Počet inštrukcií Veľkosť kódu Rýchlosť
log_y = !log_x; // 1 Byte 5 instrukcii 14 Bytov 0,44 us
int_y = int_x + 3; // 2 Byte 5 instrukcii 18 Byte 0,63 us - 2 Byte vyber z pamati, scitaj (s vyhodou ADIW - Add immediate to word) a vrat do pamati
char_y = char_x + 3; // 1 Byte 3 instrukcie 10 Byte 0,31 us
long_y = long_x + 3; // 4 Byte 11 instrukcii 38 Byte 1,25 us - lebo tu uz tahame a ukladame 4 bajty z pamati a do pamate
float_y = float_x + 3; // 4 Byte 17 instrukcii + CALL 8,06 us - volame podprogram pre floaty
double_y = double_x + 3; // 4 Byte 17 instrukcii + CALL 8,06 us - toto je presne to iste ako float<ref>A</ref>
No a takto to vyzerá zasa s tými zložitejšími operáciami[1]
// Premenna: Kod: Poznamka:
// Miesto v pamati Počet inštrukcií Veľkosť kódu Rýchlosť
int_y = int_x / 3; // 2 Byte 6 instrukcii + CALL 15,19 us - lebo na / uz nie je instrukcia
int_y = int_x / 2; // 2 Byte 9 instrukcii 26 Byte 0,81 us - lebo :2 sa da nahradit posunom vpravo >>
char_y = char_x / 3; // 1 Byte 4 instrukcie + CALL 5,38 us
long_y = long_x / 3; // 4 Byte 11 instrukcii + CALL / 39,37 us - lebo uz tahame a ukladame 4 bajty z pamati a do pamate
float_y = int_x / 3; // = ZLE = 13 instrukcii + 2xCALL! 16,88 us - tu je vysledok 0, lebo celociselne delenie len ulozime do premennej float
float_y = float_x / 3.0; // 4 Byte 17 instrukcii + CALL / 30,81 us - volame podprogram pre floaty
double_y = double_x / 3.0; // 4 Byte 17 instrukcii + CALL / 30,93 us - to iste co float
Vidíme, že operácia delenia má už za následok veľký nárast záťaže procesora. Je to tak preto, lebo na delenie neexistuje v procesoroch AVR strojová inštrukcia a tak sa musí operácia delenia nahradiť algoritmom. Pre iné procesory, napr. s jadrom ARM to nemusí byť pravda. Na druhej strane vidno, že delenie dvoma dokáže kompilátor rozpoznať a nahradiť veľmi rýchlou a účinnou inštrukciou posunu vpravo.
Upozorňujeme zvlášť na piaty riadok, kedy môže byť programátor zaskočený, že výsledkom delenia je nula, hoci použil premennú typu float. Je to však už príliš neskoro. Kompilátor najprv vypočíta pravú stranu, na ktorej vidí len celočíselné delenie a výsledok potom už len prekonvertuje do formátu IEEE a uloží do float premennej. Správny postup je o riadok nižšie, kde explicitne uvedieme, že delíme reálnym čislom.
Potrebujem vo svojom programe pracovať s reálnymi číslami!
Nie, nepotrebujete. Posúďte sami.
Na obrázku nižšie vidno signál z osciloskopu, na ktorom šírka impulzu znázorňuje dobu trvania dvoch operácií. Raz v celočíselnej aritmetike a druhý v plávajúcej rádovej čiarke. Rozdiel v rýchlosti je dvojnásobný a objem kódu je neporovnateľný. K programu, v ktorom používame plávajúucu čiarku, treba pribaliť celú knižnicu, ktorá zaberá cca 0,5-2kB (v závislosti od počtu použitých operácií).
Prvý impulz predstavuje dĺžku trvania celočíselného delenia a druhý to isté v plávajúcej aritmetike.
Aj tak potrebujem reálne čísla!
Potrebujem, lebo máme na úlohu vypočítať plnenie, ktoré sa udáva ako číslo z intervalu <0;1>. Hoci do vzorca D = Ton/(Ton+Toff) vstupujú len celé čísla, musíme vypočítať podiel, ktorý nemôžeme previesť na celé číslo. Napísal som si program, ale nefunguje mi:
Literatúra
- ↑ BB