Laborator 4 suport teoretic Tipuri de date utilizate în limbajul de programare C. Toate valorile parametrilor unei probleme, adică datele cu care operează un program, sunt reprezentate în MO sub formă codificată numeric. Datorită posibilităților tehnice/fizice de memorare a cifrelor, singura variantă posibilă pentru reprezentarea constantelor numerice este baza de numerație doi, respectiv a celor două cifre binare {0,1}. 1. Tipuri de date predefinite Limbajul de programare C/C++ recunoaște implicit numai trei tipuri de date, cu variantele respective de reprezentare internă. Diferențierea între modalitățile de reprezentare internă se face prin precizările făcute în cadrul declarațiilor, care sunt instrucțiuni de organizare a MO. De reținut că declarațiile nu determină efectuarea unor operații de calcul. Obs. Indiferent de valorile numerice pe care utilizatorul dorește să le atribuie, înscrierea unei constant în MO se efectuează numai în conformitate cu precizările din declarații. În Tabelul 1 sunt prezentate dimensiunile în octeți pentru diferitele tipuri de date predefinite. Tabelul 1 Tip variabilă C Nr. octeți (bytes) Limita inferioară Limită superioară Utilizare char 1-128 127 Caractere sau numere unsigned char 1 0 255 Numere mici pozitive short int 2-32768 +32767 Numere întregi unsigned short int 2 0 65536 Numere întregi pozitive (long) int 4-2^31 +2^31-1 Numere întregi mari unsigned (long) int 4 0 +2^32-1 Numere întregi pozitive mari 1
long long (int) 8-2^64 +2^64-1 Numere întregi foarte mari float 4-3.2*10^38 3.2*10^38 Numere reale double 8-1.7*10^308 1.7*10^308 Numere reale mari long double 12 Numere reale foarte mari void 0 - - Nici un tip Pentru alcătuirea corectă a locațiilor programatorul trebuie să introducă în program declarații adecvate asupra variabilelor ale căror valori urmează să se înscrie/memoreze. Întrucât o locație poate înscrie/memora o singură valoare, care ulterior poate fi modificată, variabilele corespunzătoare pot fi denumite și variabile simple/elementare. Obs. Alcătuirea corectă a declarațiilor impune: - atribuirea unui identificator pentru fiecare variabilă a cărei valoare se va memora. Identificatorii au o dublă semnificație abstractă și concretă. Semnificația abstractă este dată de faptul că identificatorul reprezintă un element al programului, deci o entitate sintactică. Semnificație concretă rezultă din aceea că identificatorul permite adresarea/identificarea unei locații a MO în segmentul de MO atribuit programului de către compilator; - alegerea anticipată a tipului elementar de dată care se va folosi și, corelat cu acesta, estimarea domeniului de valori care se vor atribui unei variabile în timpul executării programului. Ex.1 Să se alcătuiască declarațiile necesare memorării următoarelor constante: 32 (x, întreg fără semn) ; 24 ( y, întreg cu semn); -35678 ( z, întreg cu semn); A ( m, caracter ASCII) ; y ( n, caracter ASCII); -12.4 (p, real); 0.0005 (q, real). Precizările adăugate se referă atât la identificatorul ales pentru variabila care se va inițializa cu valoarea indicată cât și la tipul de reprezentare numerică care se folosi pentru valorile care urmează să fie memorate în respectiva locație. Aceste două informații sunt necesare pentru: - regăsirea în MO a locației atribuite respectivei variabile; - asocierea octeților necesari constituirii acestei locații, în conformitate strictă cu precizările din Tabelul 1. Având informațiile necesare se pot alcătui declarațiile din program asupra respectivelor variabile. Pentru Ex.1 Acestea sunt: unsigned x; 2
int y; long z; // sau long int z; char m,n; float p; double q; Obs. Caracterele // precizează sistemului că urmează un comentariu care are numai rol explicativ privind alcătuirea programului și este fără efect asupra execuției. a) Constantele/numerele întregi (Z) care se codifică prin codul direct (n>=0) sau codul complementar (n<0) al reprezentării în baza doi a modului numărului. Declarația int specifică faptul că o mărime/variabilă va reține numai numere întregi. Ex.2. int a; int x, a1 = 9, Ax; b) constante/numere reale (R) care se memorează codificând în baza doi, codul direct sau complementar, al unei reprezentări obținută de la forma exponențială normalizată a numărului. Declarația float specifică faptul că o mărime/variabilă va reține numai numere reale. Ex.3. float a, bc, x1a; float v = 2.3; c) constantele caracter adică alfabetul limbajului C/C++ (cifre, litere, semne speciale: A ; c ; 9 ; > ș.a.) se reprezintă prin codul ASCII. Declarația char specifică faptul că o mărime/variabilă va reține numai un cod ASCII respectiv un singur caracter. Ex.4 char m, n= a, p; char c = Ax ; Obs. Variabila n va memora numărul întreg 97, respectiv codul ASCII al literei a. Variabila c va memora numai codul primei litere 65, respectiv codul ADCII al literei A. 3
d) Deși mai puțin utilizată, se menționează și posibilitatea reprezentării numerelor naturale (N). Acestea sunt reprezentate ca numere întregi (de tip char sau int), deosebirea constând în aceea că bitul de semn (msb) de la reprezentarea numerelor întregi este folosit ca cifră a numărului. Acest lucru dublează domeniul numeric care poate fi reprezentat. Declarația unsigned specifică faptul că o mărime/variabilă va reține numai numere pozitive. Obs. Folosirea valorilor din N trebuie făcută cu foarte multă atenție întrucât atribuirea a unei valori negative determină memorarea unei valori eronate. SC nu avertizează utilizatorul, printr-un mesaj de eroare, apariția acestei erori. Toate celelalte reprezentări în MO a informațiilor se bazează pe aceste tipuri fundamentale, recunoscute implicit de calculator. 2. Domeniul de valabilitate. Programele de calcul folosesc numai următoarele tipuri de informații codificate numeric: instrucțiuni și date. Pentru memorarea acestora compilatorul atribuie locații adecvate în zone dedicate ale MO. În funcție de tipul informațiilor (date/instrucțiuni) se folosesc locații din zone distincte, iar în cadrul fiecărei zone locațiile sunt atribuite succesiv. Regăsirea unei locații se face în funcție de adresa sa, care se exprimă prin numărul de ordine al primului octet al respectivei locații. În fig. 1 este reprezentată organizarea locațiilor MO atribuite unui program. Stiva Heap Date Instrucțiuni 4
Fig.1 Memorarea instrucțiunilor. Conform regulilor de alcătuire a unui program C/C++, denumit și program sursă, instrucțiunile care îl alcătuiesc trebuie să fie grupate în entități distincte denumite funcții. După compilarea programului sursă se obține forma executabilă a programului, în care toate instrucțiunile sunt compilate/convertite în formă numerică. Aceasta este singura formă a programului care poate fi direct interpretată/executată. Instrucțiunile codificate numeric sunt memorate în locații succesive aflate în aceeași zonă a MO dar grupate conform entităților cărora le aparțin. Conform structurii lor, locațiile atribuite pentru memorarea instrucțiunilor sunt alcătuite din 1 4 o/bytes. Adresa instrucțiunii care se interpretează este preluată și înscrisă/memorată în registrul IP (Instruction Pointer) al microprocesorului. După decodificarea conținutului instrucțiunii, în IP este înscrisă adresa următoarei instrucțiuni care urmează a se executa. Astfel este realizată înlănțuirea interpretării secvențiale a unui program. Interpretarea unui program, indiferent de ordinea în care a fost alcătuit, începe cu prima instrucțiune a funcției main, care este obligatorie într-un program C/C++. Memorarea datelor. Locațiile atribuite datelor sunt grupate și pot fi accesate în funcție de poziția în program a declarațiilor care se referă la respectivele date. Aceasta determină și posibilitatea de accesare/folosire a conținutului respectivelor sunt memorate. În C/C++ sunt recunoscute următoarele tipuri de variabile: - variabile locale care sunt declarate în cuprinsul unei funcții sau în interiorul unui set de acolade {}. Se rezervă locații în timpul interpretării respectivei funcții și deci nu pot fi apelate decât în funcția respectivă. De aceea, în două funcții diferite pot exista variabile care au același identificator. Aceasta nu constituie o eroare deoarece locațiile atribuite se află în spații diferite ale stivei de date; - variabile globale care sunt declarate în afara oricărei funcții, respectiv în zona de directive. Variabilele globale, așa cum le arată și numele, sunt recunoscute și pot fi folosite în toate funcțiile programului. Tehnic/fizic acest lucru este posibil întrucât locațiile asociate sunt poziționate în zone diferite ale MO. Excepție de la această regulă face o variabilă locală care are același identificator ca și o variabilă globală. În această 5
situație, numai pentru funcția respectivă, variabila globală nu există și este înlocuită cu variabila locală. Pentru toate celelalte funcții variabila globală își păstrează caracterul global. Valabilitatea atribuirii locațiilor MO După cum s-a menționat, la apelarea unei funcții se atribuie locații variabilelor declarate în respectiva funcție. De aceea acestea sunt denumite variabile locale iar locațiile acestora fac parte din stiva de date în care se rezervă locații numai pentru variabilele locale, dar numai pe perioada executării/interpretării instrucțiunilor funcției în care sunt declarate. Evident, aceasta asigură folosirea rațională și economică a MO. De aceea informațiile înscrise în locațiile atribuite pentru variabilele locale se pot folosi numai pe perioada interpretării funcției în care au fost declarate. La reapelarea unei funcții atribuirea locațiilor pentru variabilele locale este independentă de alocarea efectuată la apelarea anterioară a funcției. Pentru eliminarea acestui inconvenient C/C++ recunoaște tipul de variabilă statică. Pentru o variabilă statică sintaxa declarației este: static int x; Declararea într-o funcție a unei variabile ca fiind statică are ca efect atribuirea unei locații în zona variabilelor globale. Aceasta permite păstrarea valorilor memorate și după încheierea interpretării funcției în care variabilele au fost declarate. Dar, spre deosebire de variabilele globale care sunt recunoscute în toate funcțiile programului, variabilele statice, deși sunt amplasate în aceeași zonă a MO, sunt recunoscute numai în timpul reluării/reapelării funcției în care sunt declarate. Aceasta se realizează deoarece adresele pentru variabilele locale ale unei funcții se stabilesc în funcție de adresa la care sunt implementate instrucțiunile funcției. Adresa la care se memorează instrucțiunile unei funcții are rolul de adresă a zonei de MO în care sunt memorate instrucțiunile funcției. Totodată această adresă este folosită și pentru determinarea adreselor variabilelor statice declarate în cadrul respectivei funcții. Acesta este un foarte bun exemplu al legăturii directe și indisolubile între instrucțiunile/precizările incluse în program și implementarea/executarea hardware/concretă a acestora. 6
3. Conversii de tip În cazul în care operanzii nu sunt de același tip sau cu reprezentare identică, conversiile de tip se efectuează implicit, dar cu respectarea anumitor reguli. Conversiile se realizează efectiv la nivelul μprocesorului, astfel încât operațiile de calcul să poată fi efectuate de structura hardware. Sunt convertite valorile operanzilor, dar fără ca valorile înscrise în locațiile MO să fie modificate. Pentru memorare rezultatul se convertește la tipul/structura declarată pentru variabila a cărei valoare se calculează, care apare în stânga semnului egal. Atunci când valoarea rezultatului nu respectă domeniul reprezentabil definit pentru tipul variabilei calculate, prin conversie se determină și se înscrie în locația MO valoare eronată. Eroarea nu se semnalizează. Obs. SC analizează și efectuează conversiile implicite în mod succesiv, operație cu operație și nu analizând ansamblul expresiei. Expresie. Ex 5. Fie secvența de instrucțiuni:... short a; int b = 70000; a = b;... Valoarea memorată în locația atribuită variabilei a este 4464. Să se explice rezultatul. Ex. 6 Fie secvența de instrucțiuni C/C++:... int a = 7, b = 2, c; c = a / b + 2.; Succesiunea interpretării tipului operanzilor și a efectuării calculelor este: - prima operație este împărțirea a/b. Întrucât ambii operanzi sunt numere întregi se efectuează operația iar rezultatul va fi un număr întreg: 7/2 =3, partea rațională (0.5) este ignorată/pierdută; 7
- urmează o operație de adunare. Operanzii sunt de tip diferit: număr întreg (3) și număr real(2.0). Operandul 3 este convertit la tipul real: 3.0. Rezultatul operației este numărul real 5.0; - operația de memorare în locația c a valorii rezultatului. Rezultatul obținut se convertește în mod obligatoriu la tipul variabilei rezultat, adică la tipul int. Rezultă c=5. Temă. Să se precizeze valoarea și tipul rezultatului atunci când variabila c era declarată de tip real. 3.1. Conversii implicite Pentru C/C++ se pot menționa următoarele situații care determină efectuarea implicită a unei conversii: - operanzii sunt de același tip (real/întreg) dar se memorează în locații cu dimensiuni diferite conversia se efectuează spre locația cu dimensiune maximă; - operanzii sunt de tip diferit: întreg și real. Regulile de conversie sunt: numerele întregii numere reale; numerele care folosesc locații mai mici formatul corespunzător locațiilor mai mari; - conversia real întreg este în general acceptată, dar partea rațională este pierdută fără a se semnaliza această eroare; - pentru operanzi numere reale conversia constă în aducerea formei interne la caracteristica cea mai mare. Aceasta presupune și repoziționarea cifrelor mantiselor. După efectuarea fiecărei operații se determină mantisa corespunzătoare formei normalizate a rezultatului și se recalculează caracteristica. Deși practic sunt fără semnificații practice, C/C++ permite evaluarea expresiilor care includ operanzi de tip int și char. Obs. Limbajul Java are definit și tipul de date boolean, reprezentat prin valorile logice true/false. Deși sunt reprezentate în locații de tip int, nu sunt admise 8
conversii de tip: boolean int sau boolean char. acestuia la alt tip de date. Cu variabilele booleene se pot efectua numai operații logice. Ex.7 Să se precizeze valorile afișate pentru b în secvența C/C++ următoare: char a = 'A', b; b = a + 2; // valoarea lui b poate fi afișată ca int sau char 3.2. Conversii explicite Pentru evitarea erorilor provocate de efectuarea unor conversii implicite, programatorul poate impune/forța conversia tipului unui operand în momentul efectuării unei operații. Pentru aceasta este definit operatorul (cast). Conversia nu afectează forma internă/memorată a operandului. Ex. 8 Pentru a evita erorile de la ex.6 se forțează schimbarea tipului unuia dintre operanzii împărțirii la tipul real. Să presupunem că se modifică tipul împărțitorului. Sintaxa expresia devine: c=7/(float)2 +2.0; Interpretarea expresiei. Prin conversia explicită se impune 2 2.0. În continuare, SC constată că trebuie să prelucreze operanzi de tip diferit: int (7) și float (2.0),ceea ce este imposibil. Se efectuează conversia implicită a deîmpărțitului:7 7.0. 7.0/2.0 =3.5 (tipul real) - se efectuează operația de adunare între două numere reale: 3.5+2.0=5.5; - conform tipului variabilei calculate c (int c) se efectuează conversia implicită 5.5 5. Valoare memorată/transferată în locația c din MO este: 5. Temă. Pentru secvențele de instrucțiuni următoare să se precizeze valoarea atribuită variabilei c. Să se explice rezultatul....... int a=10,b=3; int a=10,b=3; float c; float c; 9
c=a/b +5.5 ; c=(float)a/b +5.5; 10