This shows you the differences between two versions of the page.
programare-ca:laboratoare:lab12 [2012/12/18 17:58] mihaela.vasile |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Parametrii liniei de comandă. Preprocesorul. Funcţii cu număr variabil de parametri. ===== | ||
- | |||
- | **Responsabil:** | ||
- | * [[mihaela.vasile@gmail.com|Mihaela Vasile]] | ||
- | |||
- | ==== Obiective ==== | ||
- | |||
- | În urma parcurgerii acestui laborator, studentul va fi capabil: | ||
- | * să interpreteze şi să manipuleze parametrii liniei de comandă | ||
- | * să scrie programe care comunică cu exteriorul (utilizatorul, sistemul de operare, alte programe) prin intermediul parametrilor liniei de comandă | ||
- | * să scrie şi să folosească macrodefiniţii | ||
- | * să utilizeze macrodefiniţiile în lucrul cu mai multe fişiere | ||
- | * să evite erorile care apar frecvent în lucrul cu macrodefiniţii | ||
- | * să realizeze funcţii care primesc un număr variabil de parametri | ||
- | * să folosească macroinstrucţiunile necesare manipulării stivei de parametri a unei funcţii cu număr variabil de parametri | ||
- | |||
- | ==== Parametrii liniei de comandă ==== | ||
- | Pentru a controla execuţia unui program, de multe ori este de dorit furnizarea datelor de lucru înaintea lansării în execuţie a programului, acesta urmând să se execute apoi fără intervenţia utilizatorului (aşa-numitul „batch mode”). Acest lucru se realizează prin intermediul parametrilor liniei de comandă. (Un exemplu cunoscut este lansarea compilatorului gcc în linia de comandă cu diverse argumente, care îi spun ce şi cum sa compileze.) | ||
- | |||
- | Din punct de vedere al utilizatorului, parametrii liniei de comandă sunt simple argumente care se adaugă după numele unui program, în linia de comandă, la rularea sa. Elementele acestei liste de argumente sunt şiruri de caractere separate de spaţii. Argumentele care conţin spaţii pot fi confinate într-un singur argument prin inchiderea acestuia între ghilimele. Shell-ul este cel care se ocupă de parsarea liniei de comandă şi de crearea listei de argumente. | ||
- | |||
- | Exemplu de apelare a unui program cu argumente în linia de comandă: | ||
- | <code bash> | ||
- | gcc -Wall -I/usr/include/sys -DDEBUG -o "My Shell" myshell.c | ||
- | </code> | ||
- | |||
- | În acest caz, argumentele liniei de comandă sunt în acest caz: | ||
- | * gcc | ||
- | * -Wall | ||
- | * -I/usr/include/sys | ||
- | * -DDEBUG | ||
- | * -o | ||
- | * My Shell | ||
- | * myshell.c | ||
- | |||
- | Din punct de vedere al programatorului, parametrii liniei de comandă sunt accesibili prin utilizarea parametrilor funcţiei main(). Astfel, când se doreşte folosirea argumentelor liniei de comandă, funcţia main() se va defini astfel: | ||
- | <code c> | ||
- | int main(int argc, char *argv[]) | ||
- | </code> | ||
- | |||
- | Astfel, funcţia **main()** primeşte, în mod formal, doi parametri, un întreg şi un vector de şiruri de caractere. Numele celor două variabile nu e obligatoriu să fie **argc** şi **argv**, dar tipul lor, da. Semnificaţia lor este următoarea: | ||
- | * **int argc** (argument count) - reprezintă numărul de parametrii ai liniei de comandă. După cum se vede din exemplul anterior, există cel puţin un parametru, acesta fiind chiar numele programului (numele care a fost folosit pentru a lansa în execuţie programul - şi care poate fi diferit de numele executabilului - de exemplu prin crearea unui symlink, în Linux). | ||
- | * **char *argv[]** (arguments value) - reprezintă un vector de şiruri de caractere, având **argc** elemente (indexate de la **0** la **argc - 1**). Întotdeauna **argv[0]** conţine numele programului. | ||
- | |||
- | Parametrii liniei de comandă se pot accesa prin intermediul vectorului **argv** şi pot fi prelucraţi cu funcţiile standard de prelucrare a şirurilor de caractere. | ||
- | |||
- | ==== Preprocesorul ==== | ||
- | |||
- | Preprocesorul este componenta din cadrul compilatorului C care realizează preprocesarea. În urma acestui pas, toate instrucţiunile de preprocesare sunt înlocuite (substituite), pentru a genera cod C „pur”. Preprocesarea este o prelucrare exclusiv textuală a fişierului sursă. În acest pas nu se fac nici un fel de verificări sintactice sau semantice asupra codului sursă, ci doar sunt efectuate substituţiile din text. Astfel, preprocesorul va prelucra şi fişiere fără nici un sens în C. | ||
- | |||
- | Se consideră următorul exemplu: | ||
- | <code language="c"> | ||
- | #define EA Ana | ||
- | #define si C | ||
- | #ifdef CIFRE | ||
- | #define CINCI 5 | ||
- | #define DOUA 2 | ||
- | EA are mere. Mara are DOUA pere shi CINCI cirese. | ||
- | #endif | ||
- | Vasilica vrea sa cante o melodie in si bemol. | ||
- | </code> | ||
- | |||
- | La rularea comenzii | ||
- | <code> | ||
- | gcc -E -DCIFRE rubbish.c | ||
- | </code> | ||
- | |||
- | se va obţine următoare ieşire (se cere compilatorului să execute doar pasul de preprocesare (-E), definind în acelaşi timp şi simbolul CIFRE (-DCIFRE) : | ||
- | |||
- | <code language="c"> | ||
- | # 1 "rubbish.c" | ||
- | # 1 "<built-in>" | ||
- | # 1 "<command line>" | ||
- | # 1 "rubbish.c" | ||
- | Ana are mere. Mara are 2 pere shi 5 cirese. | ||
- | Vasilica vrea sa cante o melodie in C bemol. | ||
- | </code> | ||
- | |||
- | Cele mai importante instrucţiuni de preprocesare sunt prezentate în continuare. | ||
- | |||
- | === Incluziune === | ||
- | |||
- | Probabil cea mai des folosită instrucţiune de preprocesare este cea de incluziune, de forma | ||
- | <code language="c"> | ||
- | #include <nume_fişier> | ||
- | </code> | ||
- | sau | ||
- | <code language="c"> | ||
- | #include "nume_fisier" | ||
- | </code> | ||
- | care are ca rezultat înlocuirea sa cu conţinutul fişierului specificat de **nume_fişier**. Diferenţa dintre cele două versiuni este că cea cu paranteze unghiulare caută **nume_fişier** doar în directorul standard de fişiere antet (numit deobicei **include**), iar cea cu ghilimele caută atât în directorul **include** cât şi în directorul curent. | ||
- | |||
- | === Definirea de simboluri === | ||
- | |||
- | Definirea de simboluri este cel mai des folosită în conjuncţie cu instrucţiunile de procesare condiţionată, fiind folosită pentru activarea şi dezactivarea unor segmente de cod în funcţie de prezenţa unor simboluri. Definirea unui simbol se face în cod cu instrucţiunea | ||
- | <code language="c"> | ||
- | #define SIMBOL | ||
- | </code> | ||
- | sau se poate realiza şi la compilare, prin folosirea flagului -D al compilatorului (după cum am văzut în exemplul precedent). | ||
- | |||
- | Un simbol poate fi de asemenea „şters” folosind instrucţiunea | ||
- | <code language="c"> | ||
- | #undef SIMBOL | ||
- | </code> | ||
- | în cazul în care nu se mai doreşte prezenţa simbolului de preprocesor ulterior definirii sale. | ||
- | |||
- | === Definirea de macro-uri === | ||
- | |||
- | Instrucţiunile de preprocesare mai pot fi folosite şi pentru definirea de constante simbolice şi macroinstrucţiuni. De exemplu | ||
- | <code language="c"> | ||
- | #define CONSTANTA valoare | ||
- | </code> | ||
- | va duce la înlocuirea peste tot în cadrul codului sursă a şirului **CONSTANTA** cu şirul **valoare**. Înlocuirea nu se face totuşi în interiorul şirurilor de caractere. | ||
- | |||
- | O macroinstrucţiune este similară unei constante simbolice, ca definire, dar acceptă parametrii. Este folosită în program în mod asemănător unei funcţii, dar la compilare, ea este înlocuită în mod textual cu corpul ei. În plus, nu se face nici un fel de verificare a tipurilor. Spre exemplu: | ||
- | <code language="c"> | ||
- | #define MAX(a, b) a > b ? a : b | ||
- | </code> | ||
- | va returna maximul dintre a şi b, iar | ||
- | <code language="c"> | ||
- | #define DUBLU(a) 2*a | ||
- | </code> | ||
- | va returna dublul lui a. | ||
- | |||
- | <note important> | ||
- | Deoarece preprocesarea este o prelucrare textuală a codului sursă, în cazul exemplului de mai sus, macroinstrucţiunea în forma prezentată nu va calcula întotdeauna dublul unui număr. | ||
- | </note> | ||
- | |||
- | Astfel, la un apel de forma: | ||
- | <code language="c"> | ||
- | DUBLU(a + 3) | ||
- | </code> | ||
- | în pasul de preprocesare se va genera expresia | ||
- | <code language="c"> | ||
- | 2*a+3 | ||
- | </code> | ||
- | care bineînţeles că nu realizează funcţia dorită. | ||
- | |||
- | Pentru a evita astfel de probleme, este bine ca întotdeauna în corpul unui macro, numele „parametrilor” să fie închise între paranteze (ca de exemplu:) | ||
- | <code language="c"> | ||
- | #define SQUARE(a) (a)*(a) | ||
- | </code> | ||
- | |||
- | ==== Instrucţiuni de compilare condiţionată ==== | ||
- | |||
- | Instrucţiunile de compilare condiţionată sunt folosite pentru a „ascunde” fragmente de cod în funcţie de anumite condiţii. Formatul este următorul: | ||
- | <code language="c"> | ||
- | #if conditie | ||
- | .... | ||
- | #else | ||
- | .... | ||
- | #endif | ||
- | </code> | ||
- | unde conditie este este o expresie constantă întreagă. Pentru realizarea de expresii cu mai multe | ||
- | opţiuni se poate folosi şi forma **#elif**: | ||
- | <code language="c"> | ||
- | #if conditie | ||
- | ... | ||
- | #elif conditie2 | ||
- | ... | ||
- | #elif conditie3 | ||
- | ... | ||
- | #else | ||
- | ... | ||
- | #endif | ||
- | </code> | ||
- | |||
- | De obicei condiţia testează existenţa unui simbol. Scenariile tipice de folosire sunt: | ||
- | * dezactivarea codului de debug o dată ce problemele au fost remediate | ||
- | * compilare condiţionată în funcţie de platforma de rulare | ||
- | * prevenirea includerii multiple a fişierelor antet | ||
- | |||
- | În aceste cazuri se foloseşte forma | ||
- | <code language="c"> | ||
- | #ifdef SIMBOL | ||
- | </code> | ||
- | sau | ||
- | <code language="c"> | ||
- | #ifndef SIMBOL | ||
- | </code> | ||
- | care testează dacă simbolul SIMBOL este definit, respectiv nu este definit. | ||
- | |||
- | Prevenirea includerii multiple a fişierelor antet se realizează astfel: | ||
- | <code language="c"> | ||
- | #ifndef _NUME_FISIER_ANTET_ | ||
- | #define _NUME_FISIER_ANTET_ | ||
- | /* corpul fisierului antet */ | ||
- | /* prototipuri de functii, declaratii de tipuri si de constante */ | ||
- | #endif | ||
- | </code> | ||
- | |||
- | Astfel, la prima includere a fişierului antet, simbolul **_NUME_FISIER_ANTET_** nu este definit. Preprocesorul execută ramura **#ifndef** în care este definit simbolul **_NUME_FISIER_ANTET_** şi care conţine şi corpul - conţinutul util - al fişierului antet. La următoarele includeri ale fişierului antet simbolul **_NUME_FISIER_ANTET_** va fi definit iar preprocesorul va sări direct la sfârşitul fişierului antet, după **#endif**. | ||
- | |||
- | === Alte instrucţiuni === | ||
- | |||
- | <code language="c"> | ||
- | #pragma expresie | ||
- | </code> | ||
- | Sunt folosite pentru a controla din codul sursă comportamentul compilatorului (modul în care generează cod, alinierea structurilor, etc.) iar formatul lor diferă de la compilator la compilator. Pentru a determina ce opţiuni **#pragma** aveţi la dispoziţie consultaţi manualul compilatorului. | ||
- | |||
- | <code language="c"> | ||
- | #error MESSAGE | ||
- | </code> | ||
- | La întâlnirea acestei instrucţiuni de preprocesare compilatorul va raporta o eroare, având ca text explicativ mesajul **MESSAGE**. | ||
- | |||
- | <code language="c"> | ||
- | #line NUMBER FILENAME | ||
- | </code> | ||
- | Această instrucţiune de preprocesare modifică numărul liniei curente în valoarea specificată de **NUMBER**. În cazul în care este prezent şi parametru opţional **FILENAME** este modificat şi numele fişierului sursă curent. Astfel, mesajele de eroare şi avertismentele produse de compilator vor folosi numere de linie (şi eventual nume de fişiere) inexistente, „imaginare”, conform acestei instrucţiuni. | ||
- | |||
- | ==== Funcţii cu număr variabil de parametri ==== | ||
- | |||
- | === Introducere === | ||
- | Marea majoritate a funcţiilor discutate până acum şi totalitatea funcţiilor realizate în cadrul laboratorului | ||
- | până în acest moment primeau ca parametrii un număr prestabilit de parametrii, de tipuri bine | ||
- | precizate. Acest lucru se datorează faptului că limbajul C este un limbaj [http://en.wikipedia.org/wiki/Strongly-typed_programming_language strong-typed]. În cele mai multe cazuri acesta este un aspect pozitiv întrucât compilatorul va sesiza de la compilare situaţiile în care se încearcă pasarea unui număr eronat de parametrii sau a unor parametrii de tipuri necorespunzătoare. | ||
- | |||
- | Limbajul C permite totuşi şi declararea şi folosirea funcţiilor cu un număr (şi eventual tip) variabil de | ||
- | parametri. Astfel, numărul şi tipul tuturor parametrilor va fi cunoscut doar la rulare, biblioteca standard | ||
- | C punând la dispoziţie o serie de definiţii de tipuri şi macro definiţii care permit parcurgerea listei de | ||
- | parametri a unei funcţii cu număr variabil de parametri. Exemplul cel mai comun de astfel de funcţii | ||
- | sunt funcţiile din familia **printf()**, **scanf()**. | ||
- | |||
- | === Definirea funcţiilor cu număr variabil de parametri === | ||
- | |||
- | Prototipul funcţiilor cu număr variabil de parametrii arată în felul următor: | ||
- | <code language="c"> | ||
- | tip_rezultat nume_funcţie(listă_parametrii_fixaţi, ...); | ||
- | </code> | ||
- | |||
- | Notaţia **„ ,... ”** comunică compilatorului faptul că funcţia poate primi un număr arbitrar de parametrii | ||
- | începând cu poziţia în care apare. De exemplu, prototipul funcţiei **fprintf()** arată în felul următor: | ||
- | <code language="c"> | ||
- | void fprintf(FILE*, const char*, ...); | ||
- | </code> | ||
- | |||
- | Astfel, funcţia **fprintf()** trebuie să primească un pointer la o structură **FILE** şi un string de format şi eventual mai poate primi 0 sau mai multe argumente. Modul în care ele vor fi interpretate fiind | ||
- | determinat în cazul de faţă de conţinutul variabilei de tip **const char*** (acesta este motivul pentru care pot apărea erori la rulare în condiţiile în care încercăm să tipărim un număr întreg cu **%s** ). | ||
- | |||
- | <note important> | ||
- | O funcţie cu număr variabil de parametrii trebuie să aibă cel puţin un parametru fixat, cu nume (motivul acestei limitări rezidă în modalitatea de implementare a funcţiilor cu număr variabil de parametri, după cum se va vedea în paragraful următor). | ||
- | </note> | ||
- | |||
- | === Implementarea funcţiilor cu număr variabil de parametri === | ||
- | |||
- | Pentru prelucrarea listei de parametrii variabili este necesară includerea fişierului antet **stdarg.h**. | ||
- | Acesta conţine declaraţii pentru tipul de date variable argument list (**va_list**) şi o serie de macrodefiniţii | ||
- | pentru manipularea unei astfel de liste. În continuare este detaliat modul de lucru cu liste variabile de | ||
- | parametri. | ||
- | |||
- | * Declararea unei variabile de tip **va_list** (denumită de obicei **args**, **arguments**, **params**) | ||
- | * Iniţializarea variabilei de tip **va_list** cu lista de parametri cu care a fost apelată funcţia se realizează cu macrodefiniţia **va_start(arg_list, last_argument)** unde: | ||
- | * **arg_list** reprezintă variabila de tip **va_list** prin intermediul căreia vom accesa lista de parametri a funcţiei. | ||
- | * **last_argument** reprezintă numele ultimei variabile fixate din lista de parametri a funcţiei (în exemplul următor, aceasta este şi prima variabilă, numită **first**). | ||
- | * Accesarea variabilelor din lista de parametri se realizează cu macro-definiţia **va_arg(arg_list, type)**, unde | ||
- | * **arg_list** are aceeaşi semnificaţie ca mai sus. | ||
- | * **type** este un nume de tip şi reprezintă tipul variabilei care va fi citită. La fiecare apel se avansează în listă. În cazul în care se doreşte întoarcerea în listă, aceasta trebuie reiniţializată iar elementul dorit este accesat prin apeluri succesive de **va_arg()** | ||
- | * Eliberarea memoriei folosite de lista de parametri se realizează prin intermediul macro-definiţiei **va_end(arg_list)**, unde **arg_list** are aceeaşi semnificaţie ca mai sus. | ||
- | |||
- | **Exemplu:** Afişarea unui număr variabil de numere naturale. Lista este terminată cu un număr negativ. | ||
- | <code language="c"> | ||
- | # include <stdio.h> | ||
- | # include <stdarg.h> | ||
- | |||
- | void list_ints(int first, ...); | ||
- | |||
- | int main() { | ||
- | list_ints(-1); | ||
- | list_ints(128, 512, 768, 4096, -1); | ||
- | list_ints('b', 0xC0FFEE, 10000, 200, -2); | ||
- | /* apel corect deoarece castul la int este valid ;) */ | ||
- | list_ints(1, -1); | ||
- | return 0; | ||
- | } | ||
- | |||
- | void list_ints(int first, ...) { | ||
- | /* tratam cazul special cand nu avem nici un numar (in afara de delimitator) */ | ||
- | if (first < 0) | ||
- | { | ||
- | printf("No numbers present (besides terminator)\n"); | ||
- | return; | ||
- | } | ||
- | /*lista de parametri*/ | ||
- | va_list args; | ||
- | int current = first; | ||
- | /* initializam lista de parametri */ | ||
- | va_start(args, first); | ||
- | printf("These are the numbers (excluding terminator):\n"); | ||
- | do { | ||
- | printf("%d ", current); | ||
- | } | ||
- | /* parcurgem lista de parametri pana ce intalnim un numar negativ */ | ||
- | while ((current = va_arg(args, int)) >= 0); | ||
- | printf("\n"); | ||
- | /* curatam lista de parametrii */ | ||
- | va_end(args); | ||
- | } | ||
- | </code> | ||
- | |||
- | == Exerciţii de Laborator == | ||
- | |||