>> Inapoi <<

==========
Capitolul 15
==========
============
Instrumente soft
============
Exista doua tipuri de instrumente soft:
  - facilitati generale puse la dispozitie de catre sistemul de operare;
  - facilitati specifice desemnate explicit pentru a ajuta programatorul.
Din moment ce comenzile sistemului de operare pot fi executate dintr-un program
C, atunci programatorul poate folosi aceste comenzi ca instrumente de soft 
pentru indeplinirea anumitor sarcini. Cateva instrumente sunt disponibile 
intr-un sistem de operare, dar nu si in altul. De exemplu, "make" exista in 
UNIX, iar in MS-DOS este o trasatura ce se poate instala. Instrumentele de soft
variaza cu timpul. Sistemele de depanare par a fi cele mai disponibile. 
Compilatorul C insusi poate fi considerat un instrument soft. 

 In acest capitol vom discuta cum executam o comanda din sistemul de operare 
dintr-un program C. Apoi vom discuta cateva instrumente soft importante, cum ar
fi:
  - compilatorul C
  - "make"
  - "touch"
  - "grep"
  - instrumente de depanare

--------------------------------------------------------
Executarea comenzilor dintr-un program C  
--------------------------------------------------------
Functia "system()" (din biblioteca C) pune la dispozitie accesarea comenzilor sistemului de operare. Astfel, comenzile existente in sistemul de operare pot fi apelate si din programe C. 
-----------
Exemplu: Atat in MS-DOS, cat si in UNIX, exista comanda "date". Daca intr-un program C, scriem comanda
-----------
                system("date");
                
atunci va fi tiparita la ecran data curenta a sistemului.

Sirul trimis ca argument al functiei "system()" este tratat ca o comanda a sistemului de operare. Cand se executa instructiunea, controlul este trimis catre sistemul de operare, se executa comanda si apoi controlul este trimis inapoi catre program.
-----------
Exemplu: In UNIX, "vi" este o comanda folosita pentru editare. Presupunem ca suntem intr-un program si vrem sa editam un  
-----------  fisier al carui nume se citeste de la tastatura. Putem scrie:
         
          char comanda[MAXSTRING];  
          sprintf(comanda, "vi %s", argv[1]);
          printf("Dam comanda vi, deschizand fisierul %s\n", argv[1]);
          system(comanda);
          
Un exemplu similar poate functiona si in MS-DOS inlocuind "vi" cu alt editor de texte.
-----------
Exemplu: Consideram ca vrem sa dam comanda MS-DOS "dir" si dorim afisarea doar cu litere mici. Atunci putem scrie programul:
-----------
                #include 
                #include 
                #include 
                #define MAXSTRING 100
                
                void main()
                 {
                  char comanda[MAXSTRING], *nume_fis_temp;
                  int c;
                  FILE *ifp;  
                  nume_fis_temp = tmpnam(NULL);
                  sprintf(comanda, "dir > %s", nume_fis_temp);
                  system(comanda);
                  ifp = fopen(nume_fis_temp, "r");
                  while ((c = getc(ifp)) != EOF)
                    putchar(tolower(c));
                  remove(nume_fis_temp);
                 }

O varianta ceva mai "protejata" este:

        #include 
        #include 
        #include 

        #define MAXSTRING 100

        void main()
         {
          char comanda[MAXSTRING], *nume_fis_temp;
          int c;
          FILE *ifp;
          nume_fis_temp = tmpnam(NULL);
          sprintf(comanda, "dir > %s", nume_fis_temp);
          if (system("dir *.*") == 0)
           {
            system(comanda);
            if ((ifp = fopen(nume_fis_temp, "r")) != NULL)
               while ((c = getc(ifp)) != EOF)
                 putchar(tolower(c));
            remove(nume_fis_temp);
           }
         }
                 
Atentie ! Se creeaza intai executabilul si apoi se ruleaza dupa ce s-a iesit din compilatorul C.
-------------
Observatii: Pentru programele de mai sus, facem precizarile:
-------------
        - folosim functia "tmpnam()" pentru creearea unui nume de fisier temporar (de obicei "tmp1.$$$"); daca exista deja fisierul 
          "tmp1.$$$" in directorul curent, atunci se creeaza fisierul "tmp2.$$$",
           s.a.m.d.);
        - apelam functia "sistem()" pentru redirectarea iesirii comenzii "dir"
          in acel fisier temporar;
        - apoi tiparim continutul fisierului la ecran schimband literele mari
          in mici;
        - in final, stergem din memorie fisierul temporar folosind functia "remove()".

-------------------------
Variabile de mediu          
-------------------------
Variabilele de mediu sunt disponibile atat in UNIX, cat si in MS-DOS. Afisarea lor la ecran se poate face cu urmatorul program:

        #include 
        
        void main(int argc, char *argv[], char *env[])
         {
          int i;
          for (i = 0; env[i] != NULL; ++i)
            printf("%s\n", env[i]);
         }
         
Ambii parametri (argv si env) sunt de tip pointer catre pointer catre "char". 
Deci, putem sa-i gandim ca siruri de pointeri catre "char" sau "vectori de 
siruri de caractere". Sistemul memoreaza spatiu pentru ele. Ultimul element din
fiecare astfel de sir este pointerul NULL. Evident programul de mai sus 
foloseste doar vectorul "env". 

Pe sistemele UNIX, programul va afisa:

     PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/stefan/bin
     HOME=/home/stefan
     SHELL=/bin/bash
     TERM=vt220
     USER=stefan
     MAIL=/var/spool/mail/stefan
      . . . . .
      
La stanga semnului "=" sunt deci variabilele de mediu, iar la dreapta valorile lor, care trebuie gandite ca siruri de caractere. 
Pe sistemele MS-DOS, programul va afisa:

     PROMPT=$P$G
     PATH=Z:.;Y:.;X:.;W:.;V:.;U:.;T:.;S:.;R:.;Q:.;P:.
     COMSPEC=Y:COMMAND.COM
      . . . . . 

Ambele sisteme (UNIX si MS-DOS) pun la dispozitie comenzi pentru afisarea variabilelor de mediu. In UNIX, se pot folosi comenzile "env" sau "printenv" (pe unele sisteme si comanda "set"), iar in MS-DOS comanda "set". 

Prin conventie, variabilele de mediu sunt de obicei scrise cu litere mari. Intr-un program C, putem accesa variabilele de mediu prin al treilea argument al functiei "main()" sau putem folosi functia "getenv()" din biblioteca standard. Prototipul sau, care se gaseste in 
, este dat prin:

        char *getenv(const char *name);
        
Daca sirul trimis ca argument este o variabila de mediu, atunci functia intoarce sirul (pointer catre "char") pus la dispozitie de catre sistem ca valoare a variabilei. Daca sirul trimis ca argument nu este variabila de mediu, atunci se returneaza NULL. 
------------
Exemplu:
------------
        printf("%s%s\n%s%s\n%s%s\n%s%s\n%s%s\n",
               "Nume utilizator: ", getenv("USER"),
               "Nume login:      ", getenv("LOGNAME"),
               "Shell:           ", getenv("SHELL"),
               "Env:             ", getenv("ENV"),
               "Director Home:   ", getenv("HOME"));
               

In UNIX, anumite variabile de mediu, cum ar fi LOGNAME, SHELL, HOME, sunt puse la dispozitie de catre sistem (adica sunt nemodificabile). Pentru a initializa altele, scriem 

        setenv NAME "Abcd efgh"
        
in fisierul nostru ".login".

--------------------
Compilatorul C
--------------------
Exista multe compilatoare de C si un sistem de operare poate pune la dispozitie un numar mare de astfel de compilatoare. 
Iata cateva posibilitati: 

------------------------------------------------------------------------------
  Comanda              Compilator C apelat
------------------------------------------------------------------------------
    cc          Compilator C creat de Bell Laboratories
    cc          Compilator C creat de Cray Research (UNICOS)
    cc          Compilator C creat de Hewlett-Packard (HP-UX)
    cc          Compilator C creat de Silicon Graphics (IRIX)
    acc         Compilator C creat de Sun Microsystems (SunOS)
    gcc         Compilator GNU C creat de Free Software Foundation
    hc          Compilator High C creat de Metaware
    occ         Compilator Oregon C creat de Oregon Sofware
    qc          Compilator Quick C creat de Microsoft
    tc          Compilator Turbo C, sistem integrat, creat de Borland
    tcc         Compilator Turbo C, versiune linie comanda, Borland
------------------------------------------------------------------------------

In cele ce urmeaza, vom preciza modul de apel si optiunile acestora in UNIX. 
Multe dintre ele sunt valabile si in MS-DOS.
Daca avem un program complet intr-un singur fisier, sa zicem "pgm.c", atunci 
comanda:
                cc pgm.c
                
va traduce codul C din "pgm.c" in cod obiect executabil si-l va scrie in 
fisierul "a.out" (In MS-DOS, fisierul executabil se numeste "pgm.exe"). 
 Comanda "a.out" executa programul. 
 Consideram acum comanda: 

                cc -o pgm pgm.c
                
Aceasta cauzeaza scrierea codului executabil direct in fisierul "pgm", suprascriind-ul in cazul in care acesta exista deja (In MS-DOS optiunea similara este -e). Comanda "cc" lucreaza de fapt in trei faze:
  - apelul preprocesorului
  - apelul compilatorului
  - apelul incarcatorului (editorului de legaturi)
  
Rolul incarcatorului este de a pune (lega) impreuna bucatile furnizate de 
compilator pentru a face fisierul executabil final. Optiunea -c se foloseste 
numai pentru compilare (pentru apelul preprocesorului si compilatorului), nu si
a incarcatorului. Aceasta optiune este utila daca avem un program scris in mai 
multe fisiere. Consideram comanda

                cc -c main.c fis1.c fis2.c
                
 Daca nu sunt erori, fisierele obiect corespunzatoare vor fi create si vor avea extensia ".o" (In MS-DOS, ele au extensia ".obj"). 
 Pentru creearea unui fisier executabil, putem compila anumite fisiere cu extensia ".c" si ".o" (combinate). 
 Presupunem ca avem o eroare in "main.c". Dupa corectarea ei, putem da comanda:

                cc -o pgm main.c fis1.o fis2.o
                
Folosirea fisierului cu extensia ".o" in locul celui cu extensia ".c" reduce 
 timpul de compilare. In plus fata de extensia ".c" si ".o", putem folosi 
fisiere cu extesia ".s" care sunt create de asamblor sau de compilator cu 
optiunea "-S" (cand folosim biblioteci create de arhivator). 
Bibliotecile, de obicei, au extensia ".a" (In MS-DOS ele au extensia ".lib").

------------------------------------------------------------------------------
             Cateva optiuni folositoare pentru compilatorul C
------------------------------------------------------------------------------
-c          Doar compilare, genereaza fisiere cu extensia ".o"
-g          Genereaza cod pentru depanator
-o nume     Pune codul executabil in fisierul "nume" 
-p          Genereaza cod pentru profiler
-D nume=def Pune la inceputul fiecarui fisier cu extensia ".c" 
             linia #define nume def
-E          Apeleaza preprocesorul, dar nu si compilatorul
-I dir      (i mare) Cauta fisierele "#include" din directorul "dir"
-M          Creaza un "makefile"
-MM         Creaza un "makefile", dar nu include toate dependentele din fisierele header standard
-O          Genereaza cod optimizat
-S          Genereaza cod de asamblare in fisiere cu extensia ".s"
------------------------------------------------------------------------------

-------------------------------
Creearea unei biblioteci
-------------------------------
Multe sisteme de operare pun la dispozitie facilitati de creare si gestionare 
a bibliotecilor. In UNIX, acest lucru se face cu arhivatorul si se apeleaza cu
comanda "ar". In MS-DOS, acest lucru se realizeaza cu bibliotecarul si este o 
aplicatie ce se poate instala. Bibliotecarul Microsoft este "lib", in timp ce 
bibliotecarul Turbo C Borland este "tlib". Prin conventie, numele fisierelor 
din biblioteci au extensia ".a" in UNIX si ".lib" in MS-DOS. In cele ce urmeaza
vom discuta situatia din UNIX, dar ideea generala se poate aplica oricarui 
bibliotecar. 

In UNIX, arhivatorul "ar" poate fi folosit pentru combinarea unui grup de 
fisiere intr-unul singur numit "biblioteca". Biblioteca C standard este un 
exemplu in acest sens. Pe multe sisteme UNIX, aceasta este fisierul 
"/lib/libc.a" sau poate exista in mai multe fisiere. 
Incercati comanda:

                ar t /lib/libc.a
                
Cheia "t" este folosita pentru tiparirea numelor (sau titlurilor) fisierelor din biblioteca. Daca dorim numararea acestor titluri (pentru ca sunt foarte multe) putem da comanda:

        ar t /lib/libc.a | wc

-------------        
Observatie: Pe unele sisteme UNIX, biblioteca C nu este intitulata astfel. De exemplu, pe "fenrir", puteti incerca alt 
-------------  exemplu de biblioteca:
            
                 ar t /lib/libpwdb.a | wc

--------------------------
Folosirea lui "prof"                
--------------------------
In UNIX, daca folosim optiunea "-p" pentru compilator, atunci se produce cod 
suplimentar, care poate lua locul in fisiere obiect sau fisiere executabile 
produse de compilator. Cand programul este apelat, codul suplimentar produce 
informatii care pot fi folosite pentru generarea "profilului" unei executii. 
Informatiile pentru "profile" sunt scrise automat in fisierul "mon.out". Acest
fisier nu poate fi citit de utilizatori. Pentru a obtine informatiile din 
"mon.out", programatorul trebuie sa dea comanda

                prof pgm
                
unde "pgm" este numele programului.

---------------------------------------------
Cronometrarea executiei codului C
---------------------------------------------
Multe sisteme de operare pun la dispozitie functii pentru folosirea ceasului 
intern. Accesul la ceasul masinii este posibil in ANSI C printr-un numar de 
functii a caror prototipuri sunt descrise in . Fisierul header 
contine de asemenea un numar de alte constructii, printre care si definitiile 
lui "clock_t" si "time_t". De obicei, aceste definitii de tipuri sunt date 
prin:

        typedef long clock_t;
        typedef long time_t;
        
si aceste tipuri sunt folosite in prototipurile functiilor. Iata trei functii utile pentru cronometrarea timpului:

        clock_t clock(void);
        time_t time(time_t *p);
        double difftime(time_t time1, time_t time2);
        
Cand un program este executat, sistemul de operare tine minte timpul 
procesorului ce este folosit. Cand este apelata functia "clock()", valoarea 
returnata de sistem este cea mai buna aproximare a timpului folosit de 
program pana in acel punct. Unitatile (de masura) ceasului pot varia de la o 
masina la alta. Macro-ul

        #define CLOCKS_PER_SEC 60    /* dependent de masina */
        
este pus la dispozitie in header-ul . Acesta poate fi folosit pentru conversia valorii returnate de "clock()" catre secunde.

Functia "time()" intoarce numarul de secunde care au trecut de la 1 ianuarie 1970 (sunt posibile si alte unitati, aceasta fiind una din ele). O folosire uzuala a acestei functii este:

        srand(time(NULL));
        
Apelul se refera la generatorul de numere aleatoare. Daca trimitem doua valori produse de "time()" catre functia "difftime()", atunci va fi returnata diferenta exprimata in secunde de tip "double".

------------------------------
Programe de depanare
------------------------------
    Un program de depanare permite programatorului sa urmareasca linie cu linie 
executia codului si de a verifica valorile unor variabile sau expresii. Acest 
lucru este extrem de folositor (mai ales cand un program nu functioneaza 
conform asteptarilor). Lumea programarii este plina de programe de depanare. 
    De exemplu, in UNIX exista programul "dbx" (care insa nu este asa grozav).
Programul C "fis.c" trebuie compilat cu optiunea "-g" (debugging), dupa care se
lanseaza comanda "dbx fis.c". Pana la comanda "quit", toate comenzile sunt 
interne lui "dbx". 
Vizualizarea lor se poate face cu "dbx help".
    Un alt program de depanare ( incepand cu RedHat v4.2 ) disponibil pentru 
masinile ce ruleaza Linux este gdb ( GNU Debugger ), care este mult mai bun 
decat dbx. Acesta poate depanda un program executabil ( compilat cu parametrul
-g ) sau poate analiza fisierul core care este creat atunci cand programul se 
termina la primirea semnalului SIGSEGV ( Segmentation fault ) sau poate depana
un program in curs de executie in acest caz specificandu-se pid-ul
procesului. 
 Este invocat astfel: gdb [options] [ exec_file [ core_file or pid] ]. 

  Principalele optiune ale gdb-ului sunt:
   --core=FILE, unde FILE este numele fisierului core
   --directory=DIR, unde DIR este calea spre un director 
     in care se vor cauta surse C
 Cele mai folosite comenzi interne ale gdb-ului sunt:
   run, lanseaza in executie programul 
   list , listeaza sursa programului intre liniile nr-5 si nr+5 pe ecran
   break , seteaza un punct de intrerupere a executiei la linia nr
   next, executa urmatoarea instructiune ( si numai una )
   continue, reia executia programului din punctul in care a fost intrerupt 
   print , care afiseaza valoarea expresiei expr, unde expr poate 
    fi o variabila din program sau pur si simplu o expresie valida

Ex:
      gdb fis1 - lanseaza in executie gdb-ul "spunandu-i" sa incarce 
       simbolurile din executabilul fis1
      list 4 - va afisa sursa programului de la linia nr-5 (adica de la inceput
       ) pana la linia numarul nr+5
      break 5 - instaleaza un punct de intrerupere la linia 5
      run, lanseaza programul in executie 

 Se va lansa in executie gdb-ul "spunandu-ise" sa incarce simbolurile din 
executabilul fis1, apoi va afisa sursa programului de la linia nr-5 ( inceputul
programului ) pana la linia numaru nr+5. Instaleaza un punct de intrerupere la 
linia 5 apoi lanseaza programul in executie. Atunci cand programul ca ajunge cu
executia la linia 5, va fi intrerupt, de la acest punct utilizatorul avand 
controlul asupra variabilelor si asupra codului.

 In lumea MS-DOS, programele de depanare sunt in general incorporate. De exemplu,
firmele Microsoft si Borland produc programe de depanare excelente. 

-----------------------
Utilitarul "make"
-----------------------
Atat pentru programator, cat si pentru masina, este ineficient si costisitor sa 
pastram un program C mare intr-un singur fisier care necesita compilari 
repetate. O strategie mult mai buna este scrierea programului in mai multe 
fisiere cu extensia ".c" si compilarea lor separata. Utilitarul "make" poate fi
folosit pentru a pastra "urmele" fisierelor sursa si de a produce acces usor la
biblioteci si la fisierele header asociate. Aceasta facilitate este prezenta in
UNIX, iar in MS-DOS este o proprietate ce se poate instala. Sub Linux ( de la 
RedHat v4.2 ) la invocare make va lua comenzile ce urmeaza sa le execute din 
fisierul Makefile. 
 Formatul general al fisierului Makefile este urmatorul:

:

…
…
:


 poate fi orice comanda sau sir de comenzi valide. Daca make este 
 invocat fara parametri atunci el ca cauta eticheta 
all si va executa comenzile de acolo. Daca eticheta all nu exista atunci va fi
 semnalata eroare. Daca este invocat cu un parametru 
atunci va urma aceeasi pasi ca mai sus doar ca eticheta specificata ca 
 parametru este cautata in Makefile.

Ex:
      Pt. compilarea unui proiect ce este compus din fisierele main.c fis1.c si
  fis2.c, folosind comanda make, fisierul Makefile ar putea arata astfel:
all:
gcc main.c fis1.c fis2.c -o main

sau  (mai "prost") :

all:
gcc -c main.c -o main.o
gcc -c fis1.c -o fis1.o
gcc -c fis2.c -o fis2.o
gcc main.o fis1.o fis2.o -o main

-----------------------
Utilitarul "touch"
-----------------------
Utilitarul "touch" este disponibil intotdeauna in UNIX si uneori disponibil sub
MS-DOS (de obicei, este disponibila acolo unde este instalat "make"). 
Utilitarul "touch" este folosit pentru a actualiza data unui fisier. Acesta 
este util cand folosim "make" pentru compararea timpurilor fisierelor ce 
trebuie compilate.
-----------
Exemplu: Daca punem data curenta la un fisier "aaa.h" folosind comanda:
----------- 
                touch aaa.h
                
atunci fisierul "aaa.h" are data cea mai recenta decat toate fisierele ".h", 
".c", ".o". Acum, dand comanda "make" toate fisierele cu extensia ".c" vor fi 
recompilate si fisierele obiect linkeditate pentru a crea noul fisier 
executabil.

----------------------------------
Alte instrumente soft utile
---------------------------------
Sistemul de operare pune la dispozitie multe instrumente soft pentru programatori. Iata o lista cu cateva instrumente soft ce se gasesc in UNIX (unele chiar si in MS-DOS):
-------------------------------------------------------------------------
  Comanda                    Observatii
-------------------------------------------------------------------------
   cb       ( C Bell ) Folosit pentru transformarea codului C in 
             "pretty print"
   diff     Tipareste liniile care difera in doua fisiere
   grep     Cauta un "pattern" intr-unul sau mai multe fisiere
   indent   Alt "pretty printer" cu mai multe optiuni
   wc       Numara liniiile, cuvintele si caracterele dintr-un fisier 
             (sau mai multe)
--------------------------------------------------------------------------------------------------

Utilitarul "cb" citeste din "stdin" si scrie in "stdout". Utilitarul "indent" este mai puternic. Poate fi gasit pe versiunile UNIX Berkeley si Sun.
-----------
Exemplu:   cb  <  pgm.c
-----------

Utilitarele "diff", "grep" si "wc" pot fi folosite de oricine, nu numai de 
programatori. Cu toate ca sunt utilitare UNIX, ele sunt de obicei disponibile 
si in MS-DOS (in special "grep", foarte folositor programatorilor).  
In final, sa mentionam ca C poate fi folosit in conjunctie si cu alte 
 instrumente de nivel inalt (unele dintre ele limbaje "adevarate"):

-----------------------------------------------------------------------------------------
 Utilitar                   Observatii
-----------------------------------------------------------------------------------------
  awk      Limbaj de procesare si scanare a pattern-urilor
  csh       Acest "shell" (ca si "sh", "ksh") este programabil
  lex       Genereaza cod C pentru analiza lexicala
  sed       Editor de texte care preia comenzile sale dintr-un fisier
  yacc     "Yet another compiler-compiler", folosit la generarea de cod C
-----------------------------------------------------------------------------------------

O importanta deosebita o au "lex" si "yacc" (cu versiunile "pclex" si "pcyacc" pentru MS-DOS). Versiuni mai recente, cum ar fi, "flex" sau "bison", sunt disponibile de la Free Software Foundation, Inc. Ele lucreaza atat sub UNIX, cat si sub MS-DOS.

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Scrieti un program C care implementeaza strategiile "bubble sort", respectiv
  "quicksort", si folosind functii "de timp" comparati timpii de executie ale 
   celor doua metode.  
2. Folosind comanda "system()" scrieti un program C care apeleaza un editor de
  texte (salvati fisierul "fis.txt"), apoi listati fisierul "fis.txt" la 
  imprimanta.