This is an old revision of the document!
Responsabil:
În urma parcurgerii acestui laborator, studentul va fi capabil:
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ă:
gcc -Wall -I/usr/include/sys -DDEBUG -o "My Shell" myshell.c
În acest caz, argumentele liniei de comandă sunt în acest caz:
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:
int main(int argc, char *argv[])
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:
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 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:
#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.
La rularea comenzii
gcc -E -DCIFRE rubbish.c
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) :
# 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.
Cele mai importante instrucţiuni de preprocesare sunt prezentate în continuare.
Probabil cea mai des folosită instrucţiune de preprocesare este cea de incluziune, de forma
#include <nume_fişier>
sau
#include "nume_fisier"
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 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
#define SIMBOL
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
#undef SIMBOL
în cazul în care nu se mai doreşte prezenţa simbolului de preprocesor ulterior definirii sale.
Instrucţiunile de preprocesare mai pot fi folosite şi pentru definirea de constante simbolice şi macroinstrucţiuni. De exemplu
#define CONSTANTA valoare
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:
#define MAX(a, b) a > b ? a : b
va returna maximul dintre a şi b, iar
#define DUBLU(a) 2*a
va returna dublul lui a.
Astfel, la un apel de forma:
DUBLU(a + 3)
în pasul de preprocesare se va genera expresia
2*a+3
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:)
#define SQUARE(a) (a)*(a)
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:
#if conditie .... #else .... #endif
unde conditie este este o expresie constantă întreagă. Pentru realizarea de expresii cu mai multe opţiuni se poate folosi şi forma #elif:
#if conditie ... #elif conditie2 ... #elif conditie3 ... #else ... #endif
De obicei condiţia testează existenţa unui simbol. Scenariile tipice de folosire sunt:
În aceste cazuri se foloseşte forma
#ifdef SIMBOL
sau
#ifndef SIMBOL
care testează dacă simbolul SIMBOL este definit, respectiv nu este definit.
Prevenirea includerii multiple a fişierelor antet se realizează astfel:
#ifndef _NUME_FISIER_ANTET_ #define _NUME_FISIER_ANTET_ /* corpul fisierului antet */ /* prototipuri de functii, declaratii de tipuri si de constante */ #endif
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.
#pragma expresie
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.
#error MESSAGE
La întâlnirea acestei instrucţiuni de preprocesare compilatorul va raporta o eroare, având ca text explicativ mesajul <tt>MESSAGE</tt>.
#line NUMBER FILENAME
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.
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().
Prototipul funcţiilor cu număr variabil de parametrii arată în felul următor:
tip_rezultat nume_funcţie(listă_parametrii_fixaţi, ...);
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:
void fprintf(FILE*, const char*, ...);
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 ).
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.
Exemplu: Afişarea unui număr variabil de numere naturale. Lista este terminată cu un număr negativ.
* 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); }