Ponovitev C
Podatkovni tipi definirani v stdint.h
Pri tej vaji in v nadaljevanju semestra bomo veliko uporabljali t.i standardne celoštevilske podatkovne tipe (angl. Standard Integer Types), ki so definirani po standardu C99. Njihove definicije se nahajajo v zaglavni datoteki (angl. header file) <stdint.h>
. Tipi imajo obliko intN_t (za predznačena cela števila) in uintN_t (za nepredznačena/unsigned cela števila), kjer je N
lahko 8, 16, 32 in ponekod tudi 64.
Na voljo imamo torej tipe uint8_t
, int8_t
, uint16_t
, int16_t
, uint32_t
, int32_t
in na določenih sistemih tudi uint64_t
, int64_t
.
Uporabljali jih bomo vedno, kadar bo pomembno koliko bitov ima spremenljivka s katero operiramo. Na primer pri delu z registri naprav. Za osnovne podatkovne tipe, kot je int
, namreč nimamo zagotovila o številu bitov, ki jih za njih uporabimo na ciljnem sistemu. Večinoma sicer spremenljivke tipa int
zasedejo 32 bitov, vendar ne nujno vedno. Zaglavna datoteka pa bo poskrbela, da imajo zgoraj našteti tipi vedno toliko bitov, kot je zapisano v imenu tipa.
V funkcijo main()
dodajte spodnje vrstice in preglejte kakšne so njihove vrednosti. Ne pozabite dodatki #include <stdint.h>
. Če vas je kakšna vrednost presenetila, si skušajte razjasniti zakaj je drugačna od pričakovanj.
int8_t v = 50;
int8_t w = 129;
uint8_t p = 1;
int16_t t = -1;
int32_t k = -1;
int16_t r = 0b1001;
uint32_t x = 0x12345678;
Kazalci
Kazalci v jeziku C so pogosto trn v peti vsakega študenta. Pri predmetu Organizacija računalniških sistemov s kazalci ne bomo ustvarjali kompleksnih podatkovnih struktur, ampak bomo uporabljali njihov primarni namen – kazanje na specifični naslov. Kazalec je namreč zgolj spremenljivka, katere vrednost ni neka poljubna številka ampak predstavlja naslov. Tip kazalca nam pove kakšnega tipa je spremenljivka, ki se nahaja na naslovu, ki je “shranjen” v kazalcu.
Kazalec definiramo tako, da poleg tipa dodamo zvezdico. int*
predstavlja kazalec na spremenljivko tipa int
. Naslov torej kaže na 4 bajte, katerih vrednost predstavlja predznačeno celo število. Če imamo spremenljivko a
definirano kot int a = 4;
, kazalec na spremenljivko dobimo z ukazom int *p = &a;
. Znak &
pred imenom spremenljivke namreč vrača njen naslov. Spodaj je podana koda običajnih ukazov, ki jih izvajamo s kazalci. Razlaga je podana v komentarjih:
// definiramo kazalec
int *p;
// definiramo spremenljivko
int a = 26;
= &a; // kazalec kaze na a, recimo da se nahaja na naslovu 0x200
p *p = 12; // vrednost na naslovu 0x200 nastavimo na 12, kar pomeni da je vrednost a 12
*(p+1) = 7; // vrednost na naslovu 0x204 nastavi na 7
// v zgornjem primeru s povecavo stevca za 1 pridemo do naslova 0x204 (0x200 + 0x4), do stirice pridemo, ker je na naslovu 0x200 spremenljivka tipa int, ki zaseda 4 bajte
Kazalce bomo pri ORS uporabljali predvsem takrat, ko bomo želeli pisati na točno določene naslove. Ali pa, ko bomo želeli da funkcija spreminja vrednost podanega argumenta. Primer takšne uporabe je prikazan spodaj. Funkciji fun_value
podamo vrednost spremenljivke x
, ki jo potem v funkciji spremenimo. Ker je bila spremenljivka podana kot vrednost, se ta sprememba ne pozna v funkciji, ki kliče fun_value
. V primeru, da si želimo takšnega obnašanja, moramo argument x
podati kot kazalec, v funkciji pa spreminjati vrednost na naslovu na katerega kaže ta kazalec. Tak primer je prikazan v funkciji fun_reference
.
void fun_value(int a) {
// ...
= 4;
a // ...
}
void fun_reference(int* a) {
// ...
*a = 7;
// ...
}
int main() {
int x = 1;
(x);
fun_value// x je tu se vedno 1
(&x);
fun_reference// x je sedaj 7 tudi v main funkciji
}
Strukture
Struktura v jeziku C je zbirka spremenljivk različnih osnovnih tipov.
struct circle {
int x;
int y;
uint32_t z;
float r;
uint16_t a;
uint16_t b;
double c;
};
struct circle a;
.x = 4;
a.y = 6;
a
struct circle *b;
->x = 3;
b->y = 7; b
Posebnost struktur, ki jo bomo s pridom izkoriščali tekom semestra, je ta, da se elementi strukture v pomnilniku vedno nahajajo zaporedno. Če se zgornja struktura začne na naslov 0x200
, potem vemo, da se element x
nahaja na naslovu 0x200
, element y
na naslovu 0x204
, element z
na naslovu 0x208
, element r
na naslovu 0x20C
, element a
na naslovu 0x210
, element b
na naslovu 0x212
, ter element c
na naslovu 0x214
.
Druga posebnost struktur je njihova uporaba v kombinaciji s kazalci. Kazalec na strukturo definiramo enako kot vsak drug kazalec, kar lahko vidite v zgornjem primeru. Naivno bi tako lahko pričakovali, da bomo do posameznih elementov dostopali z *b.x = 3;
. Težava nastopi, ker ima pika najvišjo prioriteto med operatorji zato bi to morali zapisati kot (*b).x
, kar pa je precej neberljivo in zoprno za pisati. Namesto tega uporabimo bolj pregledno obliko s puščico: b->x
.
Bitne operacije
C omogoča sledeče operacije nad biti: - bitni in: x & y
- bitni ali: x | y
- bitni xor: x ^ y
- bitna negacija: ~x
- pomik bitov v desno x >> 2
- pomik bitov v levo x << 3
Pri pomiku bitov v levo, se izpraznjeni bit vedno postavi na vrednost 0. Pri pomiku v desno se na izpraznjeno mesto pri nepredznačenih številih vpiše ničla, pri predznačenih pa se na izpraznjeno mesto vpiše vrednost zadnjega bita pred pomikom (predznaka). Bitne operacije so pri delu z mikrokrmilniki nepogrešsljive. Z njimi bomo izvajali predvsem sledeče operacije:
Postavi bit (Set Bits)
Pogosto bomo želeli bit b_i
v n
-bitni spremenljivki spremeniti v enico, pri tem pa ostale bite ohraniti na istem stanju. Na ta način bomo na primer prižgali LED diodo, zagnali motorček, itd. To najlažje dosežemo z operacijo ali a = a | x;
, kjer je x
konstanta v kateri je zgolj bit b_i
nastavljen na 1. Takšno število najlažje ustvarimo tako, da enico pomaknemo za i
mest v levo. Ukaz, ki v spremenljivki a
nastavi bit b_i
je tako:
= a | (1 << i); a
Pobriši bit (Reset Bits)
Seveda bomo želeli na enak način bite tudi pobrisati oz. ponastaviti, torej vrednost bita b_i
v n
-bitnem številu spremeniti v ničlo, ostale bite pa ohraniti v istem stanju. Najenostavneje to dosežemo z operacijo in - a = a & x;
, kjer je x
konstanta v kateri je zgolj bit b_i
nastavljen na 0, ostali biti pa na 1. Takšno konstanto najlažje ustvarimo tako, da enico pomaknemo za i
mest, kot v prejšnjem primeru, nato pa dobljeno konstanto negiramo. Ukaz, ki spremenljivki a
pobriše bit b_i
je tako:
= a & ~(1 << i); a
Negiraj bit (Toggle Bits)
Z operacijo negacije obrnemo vrednost posameznega bita pri čemer stanje ostalih bitov ohranimo. To dosežemo na enak način kot pri postavljanju bita, le da namesto operacije ali uporabimo operacijo ekskluzivni ali xor:
= a ^ (1 << i); a
Testiraj bit (Test Bits)
Velikokrat nas bo zanimala vrednost bita b_i
v n
-bitni spremenljivki. Na ta način bomo na primer ugotavljali ali je gumb pritisnjen ali spuščen. Najenostavnejši postopek za testiranje vrednosti bita b_i
je:
// testiraj bit i
& (1 << i);
a
// primer testiranja bita j
if (a & (1 << j)) {
// ce je j-ti bit enica
} else {
// ce je j-ti bit nicla
}
Naloga za ponovitev
Implementirajte naslednje funkcije:
reset_bit(uint32_t x, uint8_t p)
– resetiraj p-ti bit spremenljivke x.reset_two_bits(uint32_t x, uint8_t p)
– resetiraj bita p in p+1 spremenljivke x.set_bit(uint32_t x, uint8_t p)
– postavi p-ti bit spremenljivke xset_two_bits_to(uint32_t x, uint8_t p, uint8_t n)
– bita p in p+1 spremenljivke x nastavi na vrednost n - možne vrednosti so 0 do 3set_vector_length(struct vector a*)
, ki prejme kazalec na strukturostruct vector
s tremi elementi:x
,y
,length
. Prva dva elementa sta predznačeni celi števili,length
pa število v plavajoči vejici. Funkcijavector_length
na podlagi vrednostix
iny
izračuna dolžino vektorjalength = sqrt(x^2+y^2)
in jo zapiše vlength
. Funkcija nič ne vrača.
uint32_t a;
uint32_t b;
= 0xf;
a = reset_bit(a, 2); // 0xB
b = 0xA;
a = reset_bit(a, 0); // 0xA
b
= 0xFF;
a = reset_two_bits(a, 3); // 0xE7
b = 0xB7;
a = reset_two_bits(a, 3); // 0xA7
b
= 0xB;
a = set_bit(a, 0); // 0xB
b = 0xE;
a = set_bit(a, 2); // 0xE
b
= 0xEF;
a = set_two_bits_to(a, 3, 1); // 0xEF
b = 0xB7;
a = set_two_bits_to(a, 3, 2); // 0xB7
b
struct vector c;
.x = 4;
c.y = -2;
c(&c);
set_vector_length// c.length = 4.47