This is an old revision of the document!


Parametrii liniei de comandă. Preprocesorul. Funcţii cu număr variabil de parametri.

Responsabil:

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ă:

gcc -Wall -I/usr/include/sys -DDEBUG -o "My Shell" myshell.c

Î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:

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:

  • 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:

#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.

Incluziune

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

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.

Definirea de macro-uri

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.

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.

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ţ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:

#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:

  • 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

#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.

Alte instrucţiuni

#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 MESSAGE.

#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.

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:

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 ).

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).

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.

  # 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);
}
Exerciţii de Laborator
programare-ca/laboratoare/lab12.1355846307.txt.gz · Last modified: 2012/12/18 17:58 by mihaela.vasile
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0