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