Laboratorul de Sisteme de Operare este unul de programare de sistem având drept scop aprofundarea conceptelor prezentate la curs și prezentarea interfețelor de programare oferite de sistemele de operare (system API). Un laborator va prezenta un anumit set de concepte și va conține următoarele activități:
Pentru o desfășurare cât mai bună a laboratorului și o înțelegere deplină a conceptelor vă recomandăm să parcurgeți conținutul laboratorului de acasă. De asemenea, pentru consolidarea cunoștințelor folosiți suportul de laborator prezentat în paragraful următor.
Pentru a oferi o arie de cuprindere cât mai largă, laboratoarele au ca suport familiile de sisteme de operare Unix
și Windows
. Instanțele de sisteme de operare din familiile de mai sus alese pentru acest laborator sunt GNU/Linux
, respectiv Windows 7
.
În cadrul acestui laborator introductiv va fi prezentat mediul de lucru care va fi folosit în cadrul laboratorului de Sisteme de Operare cât și în rezolvarea temelor de casă.
Laboratorul folosește ca suport de programare limbajul C/C++
. Pentru GNU/Linux
se va folosi suita de compilatoare GCC
, iar pentru Windows
compilatorul Microsoft pentru C/C++ cl
. De asemenea, pentru compilarea incrementală a surselor se vor folosi GNU make
(Linux), respectiv nmake
(Windows). Exceptând apelurile de bibliotecă standard, API-ul folosit va fi POSIX, respectiv Win32.
GCC este suita de compilatoare implicită pe majoritatea distribuțiilor Linux. Pentru mai multe detalii despre proiectul GCC apăsați pe butonul Click to display
(de acum înainte secțiunile suplimentare vor fi ascunse folosind astfel de butoane).
În cadrul laboratoarelor de Sisteme de Operare ne vom concentra asupra facilităților oferite de compilator pentru limbajele C și C++. GCC are suport pentru stadardele ANSI
, ISO C
, ISO C99
, POSIX
, dar și multe extensii folositoare care nu sunt incluse în niciunul din standarde; unele dintre aceste extensii vor fi prezentate în secțiunile ce urmează.
Vom folosi pentru exemplificare un program simplu care tipărește la ieșirea standard un șir de caractere.
GCC folosește pentru compilarea de programe C/C++ comanda gcc
, respectiv g++
. O invocare tipică este pentru compilarea unui program dintr-un singur fișier sursă, în cazul nostru hello.c
.
so@spook$ ls hello.c so@spook$ gcc hello.c so@spook$ ls a.out hello.c so@spook$ ./a.out SO, ... hello world! |
so@spook$ ls hello.c so@spook$ gcc hello.c -o hello so@spook$ ls hello hello.c so@spook$ ./hello SO, ... hello world! |
Așadar, comanda gcc hello.c
a fost folosită pentru compilarea fișierului sursă hello.c
. Rezultatul a fost obținerea fișierului executabil a.out
(nume implicit utilizat de gcc
). Dacă se dorește obținerea unui executabil cu un alt nume se poate folosi opțiunea -o
.
În mod similar se poate folosi g++
pentru compilarea unui program sursă C++.
Compilarea se referă la obținerea unui fișier executabil dintr-un fișier sursă. După cum am văzut în paragraful anterior comanda gcc
a dus la obținerea fişierului executabil hello
din fişierul sursă hello.c
. Intern, gcc
trece prin mai multe faze de prelucrare a fişierului sursă până la obținerea executabilului. Aceste faze sunt evidențiate în diagrama de mai jos:
Implicit, la o invocare a comenzii gcc
/g++
se obţine din fişierul sursă un executabil. Folosind diverse opțiuni, putem opri compilarea la una din fazele intermediare astfel:
-E
- se realizează doar preprocesarea fişierului sursăgcc -E hello.c
– va genera fişierul preprocesat pe care, implicit, îl va afişa la ieşirea standard.-S
- se realizează inclusiv faza de compilaregcc -S hello.c
– va genera fişierul în limbaj de asamblare hello.s
-c
- se realizează inclusiv faza de asamblaregcc -c hello.c
– va genera fişierul obiect hello.o
Opţiunile de mai sus pot fi combinate cu -o
pentru a specifica fişierul de ieşire.
Preprocesarea presupune înlocuirea directivelor de preprocesare din fişierul sursă C. Directivele de preprocesare încep cu #
. Printre cele mai folosite sunt:
#include
– pentru includerea fişierelor header într-un alt fișier.#define
și #undef
– pentru definirea, respectiv anularea definirii de macrouri.#if
, #ifdef
, #ifndef
, #else
, #elif
, #endif
, pentru compilarea condiţionată.do_evil_things
de mai jos nu putem folosi comentarii de tip C, ca în exemplul din dreapta, întrucat limbajul C nu permite comentariile imbricate. În astfel de cazuri se poate folosi directiva #if <condiţie>
ca în exemplul din stânga.
#if 0 int do_evil_things(context_t *ctx) { int go_drink; /* set student mode ON :) */ ctx->go_drink = NO; } #endif |
/* int do_evil_things(context_t *ctx) { int go_drink; /* set student mode ON :) */ ctx->go_drink = NO; } * |
__FILE__
, __LINE__
, __func__
sunt înlocuite cu numele fişierului, linia curentă în fișier şi numele funcției #
este folosit pentru a înlocui o variabilă transmisă unui macro cu numele acesteia.
#include <stdio.h> #define show_var(a) printf("Variable %s has value %d\n", #a, a); int main(void) { int teh_var = 42; show_var(teh_var); return 0; } |
so@spook$ gcc -o show show.c so@spook$ ls show show.c so@spook$ ./show Variable teh_var has value 42 |
##
(token paste) este folosit pentru concatenarea între un argument al macrodefiniţiei și un alt şir de caractere sau între două argumente ale macrodefiniţiei. De multe ori, un dezvoltator va dori să poată activa sau dezactiva foarte facil afişarea de mesaje suplimentare (de informare sau de debug) în sursele sale.
Compilarea este faza în care din fişierul preprocesat se obţine un fişier în limbaj de asamblare.
so@spook$ ls hello.c so@spook$ gcc -S hello.c so@spook$ ls hello.c hello.s
Asamblarea este faza în care codul scris în limbaj de asamblare este tradus în cod mașină reprezentând codificarea binară a instrucțiunilor programului iniţial. Fişierul obţinut poartă numele de fişier cod obiect, se obţine folosind opţiunea -c
a compilatorului şi are extensia .o
.
so@spook$ ls hello.c so@spook$ gcc -c hello.c so@spook$ ls hello.c hello.o
Pentru obținerea unui fişier executabil este necesară rezolvarea diverselor simboluri prezente în fişierul obiect. Această operaţie poartă denumirea de editare de legături, link-editare, linking sau legare.
void f(void); /* * no definition for f here */ int main(void) { f(); return 0; } |
void f(void); void f(void) { } int main(void) { f(); return 0; } |
so@spook$ ls sample.c so@spook$ gcc -c -o sample sample.c so@spook$ ls sample.c sample.o so@spook$ gcc -o sample sample.c /tmp/ccOVreJg.o: In function `main': sample.c:(.text+0x7): undefined reference to `f' collect2: ld returned 1 exit status |
so@spook$ ls sample.c so@spook$ gcc -c -o sample sample.c so@spook$ ls sample.c sample.o so@spook$ gcc -o sample sample.c so@spook$ ls sample sample.c sample.o |
Observăm că în partea stângă deși am obținut fișierul obiect sample.o
, linkerul nu poate genera fişierul executabil întrucât nu găseşte definiţia funcţiei f
. În partea dreaptă totul decurge normal, definiţia funcţiei f
fiind inclusă în fişierul sursă.
În mod implicit, o rulare a gcc
oferă puține avertismente utilizatorului. Pentru a activa afișarea de avertismente se folosesc opțiunile de tip -W
cu sintaxa -Woptiune-avertisment
. optiune-avertisment
poate lua mai multe valori posibile printre care return-type
, switch
, unused-variable
, uninitialized
, implicit
, all
. Folosirea opțiunii -Wall
înseamnă afișarea tuturor avertismentelor care pot cauza inconsistențe la rulare.
Considerăm ca fiind indispensabilă folosirea opțiunii -Wall
pentru a putea detecta încă din momentul compilării posibilele erori. O cauză importantă a aparițiilor acestor erori o constituie sintaxa foarte permisivă a limbajului C. Sperăm ca exemplul de mai jos să justifice utilitatea folosirii opțiunii -Wall
:
so@spook$ ls middle.c so@spook$ gcc -o middle middle.c so@spook$ ./middle Middle of interval [10, 20] is 10 so@spook$ gcc -Wall -o middle middle.c middle.c: In function ‘main’: middle.c:8: warning: suggest parentheses around ‘+’ inside ‘>>’ |
La prima rulare, rezultatul nu e nici pe departe cel așteptat. Eroarea poate fi detectată ușor dacă includem și opțiunea -Wall
la compilare. (operatorul +
are prioritate în fața operatorului >>
)
Exemplele de până acum tratează programe scrise într-un singur fișier sursă. În realitate, aplicațiile sunt complexe și scrierea întregului cod într-un singur fișier îl face greu de menținut și greu de extins. În acest sens aplicația este scrisă în mai multe fișiere sursă denumite module. Un modul conține, în mod obișnuit, funcții care îndeplinesc un rol comun.
Următoarele fișiere sunt folosite ca suport pentru a exemplifica modul de compilare a unui program provenind din mai multe fișiere sursă:
|
|
În programul de mai sus se apelează funcțiile f1
și f2
în funcția main
pentru a afișa diverse informații. Pentru compilarea acestora se transmit toate fișierele C ca argumente către gcc
:
so@spook$ ls f1.c f2.c main.c util.h so@spook$ gcc -Wall main.c f1.c f2.c -o main so@spook$ ls f1.c f2.c main main.c util.h so@spook$ ./main Current file name f1.c Current line 8 in file f2.c
Executabilul a fost denumit main
; pentru acest lucru s-a folosit opțiunea -o
.
Se observă folosirea fișierului header util.h
pentru declararea funcțiilor f1
și f2
. Declararea unei funcții se realizează prin precizarea antetului. Fișierul header este inclus în fișierul main.c
pentru ca acesta să aibă cunoștință de formatul de apel al funcțiilor f1
și f2
. Funcțiile f1
și f2
sunt definite, respectiv, în fișierele f1.c
și f2.c
. Codul acestora este integrat în executabil în momentul link-editării.
În general, pentru obținerea unui executabil din surse multiple se obișnuiește compilarea fiecărei surse până la modul obiect și apoi link-editarea acestora:
so@spook$ ls f1.c f2.c main.c util.h so@spook$ gcc -Wall -c f1.c so@spook$ gcc -Wall -c f2.c so@spook$ gcc -Wall -c main.c so@spook$ ls f1.c f1.o f2.c f2.o main.c main.o util.h so@spook$ gcc -o main main.o f1.o f2.o so@spook$ ls f1.c f1.o f2.c f2.o main main.c main.o util.h so@spook$ ./main Current file name f1.c Current line 8 in file f2.c
Se observă obținerea executabilului main
prin legarea modulelor obiect. Această abordare are avantajul eficienței. Dacă se modifică fișierul sursă f2.c
atunci doar acesta va trebui compilat și refăcută link-editarea. Dacă s-ar fi obținut un executabil direct din surse atunci s-ar fi compilat toate cele trei fișiere și apoi refăcută link-editarea. Timpul consumat ar fi mult mai mare, în special în perioada de dezvoltare când fazele de compilare sunt dese și se dorește compilarea doar a fișierelor sursă modificate.
Scăderea timpului de dezvoltare prin compilarea numai a surselor care au fost modificate este motivația de bază pentru existența utilitarelor de automatizare precum make
sau nmake
.
O bibliotecă este o colecție de funcții precompilate. În momentul în care un program are nevoie de o funcție, linker-ul va apela respectiva funcție din bibliotecă. Numele fișierului reprezentând biblioteca trebuie să aibă prefixul lib:
so@spook$ ls -l /usr/lib/libm.* -rw-r--r-- 1 root root 496218 2010-01-03 15:19 /usr/lib/libm.a lrwxrwxrwx 1 root root 14 2010-01-14 12:17 /usr/lib/libm.so -> /lib/libm.so.6
Biblioteca matematică este denumită libm.a
sau libm.so
. În Linux bibliotecile sunt de două tipuri:
Legarea se face folosind opțiunea -l
transmisă comenzii gcc
. Astfel, dacă se dorește folosirea unor funcții din math.h
, trebuie legată biblioteca matematică:
so@spook$ ls cbrt.c so@spook$ gcc -Wall -o cbrt cbrt.c /tmp/ccwvm1zq.o: In function `main': cbrt.c:(.text+0x1b): undefined reference to `cbrt' collect2: ld returned 1 exit status so@spook$ gcc -Wall -o cbrt -lm cbrt.c so@spook$ ./cbrt Cubic root for 1000 is 10 |
Se observă că, în primă fază, nu s-a rezolvat simbolul cbrt
. După legarea bibliotecii matematice, programul s-a compilat și a rulat fără probleme.
Pentru crearea de biblioteci vom folosi fișierele din secțiunea Compilarea din mai multe fișiere. Vom include modulele obiect rezultate din fișierele sursă f1.c și f2.c într-o bibliotecă pe care o vom folosi ulterior pentru obținerea executabilului final.
Primul pas constă în obținerea modulelor obiect asociate:
so@spook$ gcc -Wall -c f1.c so@spook$ gcc -Wall -c f2.c
O bibliotecă statică este o arhivă ce conține fișiere obiect creată cu ajutorul utilitarului ar ( interpretați parametrii rc
).
so@spook$ ar rc libintro.a f1.o f2.o so@spook$ gcc -Wall main.c -o main -lintro /usr/bin/ld: cannot find -lintro collect2: ld returned 1 exit status |
so@spook$ gcc -Wall main.c -o main -lintro -L. so@spook$ ./main Current file name is f1.c Current line 5 in file f2.c |
Atenție: -lintro trebuie să apară după specificarea sursei și a fișierului executabil
Linker-ul returnează eroare precizând că nu găsește biblioteca libintro
. Aceasta deoarece linker-ul nu a fost configurat să caute și în directorul curent. Pentru aceasta se folosește opțiunea -L
, urmată de directorul în care trebuie căutată biblioteca (în cazul nostru este vorba de directorul curent).
Dacă biblioteca se numește libnume.a
, atunci ea va fi referită cu -lnume
Spre deosebire de o bibliotecă statică despre care am văzut că nu este nimic altceva decât o arhivă de fișiere obiect, o bibliotecă partajată este ea însăși un fișier obiect. Crearea unei biblioteci partajate se realizează prin intermediul linker-ului. Optiunea -shared
indică compilatorului să creeze un obiect partajat și nu un fișier executabil. Este, de asemenea, indicată folosirea opțiunii -fPIC
la crearea fișierelor obiect.
so@spook$ gcc -fPIC -c f1.c so@spook$ gcc -fPIC -c f2.c so@spook$ gcc -shared f1.o f2.o -o libintro_shared.so so@spook$ gcc -Wall main.c -o main -lintro_shared -L. so@spook$ ./main ./main: error while loading shared libraries: libintro_shared.so: cannot open shared object file: No such file or directory
La rularea executabilului se poate observa că nu se poate încărca biblioteca partajată. Cauza este deosebirea dintre bibliotecile statice și bibliotecile partajate. În cazul bibliotecilor statice codul funcției de bibliotecă este copiat în codul executabil la link-editare. De partea cealaltă, în cazul bibliotecilor partajate, codul este încărcat în memorie în momentul rulării.
Astfel, în momentul rulării unui program, loader-ul (programul responsabil cu încărcarea programului în memorie), trebuie să știe unde să caute biblioteca partajată pentru a o încărca în memorie în cazul în care aceasta nu a fost încărcată deja. Loader-ul folosește câteva căi predefinite (/lib, /usr/lib etc) și de asemenea locații definite în variabila de mediu LD_LIBRARY_PATH
:
so@spook$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. so@spook$ ./main Current file name is f1.c Current line 5 in file f2.c
În exemplul de mai sus variabilei de mediu LD_LIBRARY_PATH
i-a fost adăugată calea către directorul curent rezultând în posibilitatea rulării programului. LD_LIBRARY_PATH
va rămâne modificată cât timp va rula consola curentă. Pentru a face o modificare a unei variabile de mediu doar pentru o instanță a unui program se face atribuirea noii valori înaintea comenzii de execuție:
so@spook$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./main Fisierul curent este f1.c Va aflati la linia 5 din fisierul f2.c so@spook$ ./main ./main: error while loading shared libraries: libintro_shared.so: cannot open shared object file: No such file or directory
Make este un utilitar care permite automatizarea și eficientizarea sarcinilor. În mod particular este folosit pentru automatizarea compilării programelor. După cum s-a precizat, pentru obținerea unui executabil provenind din mai multe surse este ineficientă compilarea de fiecare dată a fiecărui fișier și apoi link-editarea. Se compilează fiecare fișier separat, iar la o modificare se va recompila doar fișierul modificat.
Utilitarul make folosește un fișier de configurare denumit Makefile
. Un astfel de fișier conține reguli și comenzi de automatizare.
|
so@spook$ make gcc -Wall hello.c -o hello so@spook$ ./hello SO, ... hello world! |
so@spook$ make clean rm -f hello so@spook$ make all gcc -Wall hello.c -o hello |
Exemplul prezentat mai sus conține două reguli: all
și clean
. La rularea comenzii make
se execută prima regulă din Makefile (în cazul de față all
, nu contează în mod special denumirea). Comanda executată este gcc -Wall hello.c -o hello
. Se poate preciza explicit ce regulă să se execute prin transmiterea ca argument comenzii make
. (comanda make clean
pentru a șterge executabilul hello
și comanda make all
pentru a obține din nou acel executabil).
În mod implicit, GNU Make caută, în ordine, fișierele GNUmakefile, Makefile, makefile și le analizează. Pentru a preciza ce fișier Makefile trebuie analizat, se folosește opțiunea -f
. Astfel, în exemplul de mai jos, folosim fișierul Makefile.ex1:
so@spook$ mv Makefile Makefile.ex1 so@spook$ make make: *** No targets specified and no makefile found. Stop. so@spook$ make -f Makefile.ex1 gcc -Wall hello.c -o hello so@spook$ make -f Makefile.ex1 clean rm -f hello
În continuare este prezentată sintaxa unei reguli dintr-un fișier Makefile:
Un exemplu indicat pentru un fișier Makefile
este:
all: hello hello: hello.o gcc hello.o -o hello hello.o: hello.c gcc -Wall -c hello.c clean: rm -f *.o *~ hello
Se observă prezența regulii all
care va fi executată implicit.
hello
și nu execută nicio comandă; hello.o
și realizează link-editarea fișierului hello.o
; hello.c
și realizează compilarea și asamblarea fișierului hello.c
. Pentru obținerea executabilului se folosește comanda:
so@spook$ make -f Makefile.ex2 gcc -Wall -c hello.c gcc hello.o -o hello
Pentru obținerea unui target trebuie satisfăcute dependențele (prerequisites) acestuia. Astfel, pentru obținerea targetului implicit (primul target), în cazul nostru all
:
all
trebuie obținut target-ul hello
, care este un nume de executabilhello
trebuie obținut target-ul hello.o
hello.o
trebuie obținut hello.c
; acest fișier există deja, și cum acesta nu apare la rândul lui ca target în Makefile
, nu mai trebuie obținuthello.o
; aceasta este gcc -Wall -c hello.c
hello.o
, care este folosit ca dependență pentru hello
gcc hello.o -o hello
pentru obținerea executabilului hello
hello
este folosit ca dependență pentru all
; acesta nu are asociată nicio comandă deci este automat obținut.
De remarcat este faptul că un target nu trebuie să aibă neapărat numele fișierului care se obține. Se recomandă, însă, acest lucru pentru înțelegerea mai ușoară a fișierului Makefile
, și pentru a beneficia de faptul că make
utilizează timpul de modificare al fișierelor pentru a decide când nu trebuie să facă nimic.
Acest format al fișierului Makefile
are avantajul eficientizării procesului de compilare. Astfel, după ce s-a obținut executabilul hello conform fișierului Makefile
anterior, o nouă rulare a make nu va genera nimic:
so@spook$ make -f Makefile.ex2 make: Nothing to be done for 'all'.
Un fișier Makefile
permite folosirea de variabile. Astfel, un exemplu uzual de fișier Makefile
este:
CC = gcc CFLAGS = -Wall -g all: hello hello: hello.o $(CC) $^ -o $@ hello.o: hello.c $(CC) $(CFLAGS) -c $< .PHONY: clean clean: rm -f *.o *~ hello
În exemplul de mai sus au fost definite variabilele CC
și CFLAGS
. Variabila CC
reprezintă compilatorul folosit, iar variabila CFLAGS
reprezintă opțiunile (flag-urile) de compilare utilizate; în cazul de față sunt afișarea avertismentelor și compilarea cu suport de depanare. Referirea unei variabile se realizează prin intermediul construcției $(VAR_NAME). Astfel, $(CC)
se înlocuiește cu gcc
, iar $(CFLAGS)
se înlocuiește cu -Wall -g
.
Variabile predefinite folositoare sunt:
Pentru mai multe detalii despre variabile consultați pagina info [1] sau manualul online [2]
De foarte multe ori nu este nevoie să se precizeze comanda care trebuie rulată; aceasta poate fi detectată implicit.
main.o: main.c |
so@spook$ $(CC) $(CFLAGS) -c -o $@ $< |
Astfel, fișierul Makefile.ex2 de mai sus poate fi simplificat, folosind reguli implicite, ca mai jos:
so@spook$ make -f Makefile.ex4 gcc -Wall -g -c -o hello.o hello.c gcc hello.o -o hello |
so@spook$ make -f Makefile.ex5 gcc -Wall -g -c -o hello.o hello.c gcc hello.o -o hello |
De remarcat faptul că dacă avem un singur fișier sursă nici nu trebuie să existe un fișier Makefile
pentru a obține executabilul dorit.
so@spook$ls hello.c so@spook$ make hello cc hello.c -o hello
Pentru mai multe detalii despre reguli implicite consultați pagina info [3] sau manualul online [4].
Există câteva unelte GNU care pot fi folosite atunci când nu reușim să facem un program să ne asculte. gdb, acronimul de la “Gnu DeBugger” este probabil cel mai util dintre ele, dar există și altele, cum ar fi ElectricFence
, gprof sau mtrace
. gdb este prezentat pe scurt aici.
Soluția folosită pentru platforma Windows în cadrul acestui laborator este cl.exe
, compilatorul Microsoft pentru C/C++. Recomandăm instalarea Microsoft Visual C++ Express 2010 (10.0) (versiunea Professional a Visual C++ este disponibilă gratuit în cadrul MSDNAA). Programele C/C++ pot fi compilate prin intermediul interfeței grafice sau în linie de comandă. În cele ce urmează vom prezenta compilarea folosind linia de comandă. În Windows fișierele cod obiect au extensia *.obj
.
cl hello.c $ cl /? /* list of options for compiler */ $ link /? /* list of options for linker */ |
Se vor prezenta mai jos o serie de opțiuni uzuale:
Opțiuni privind optimizarea codului:
|
Setarea numelui pentru diferite fișiere de ieșire:
|
Exemple:
myobj.obj
din sursa mysrc.c
: cl /Fomyobj.obj /c mysrc.c
myasm.asm
în cod de asamblare din sursa mysrc.c
: cl /Famyasm.asm /FA /c mysrc.c
Lista completă de opțiuni o puteți găsi aici
Pentru a crea biblioteci statice se folosește comanda lib
>lib /out:<nume.lib> <lista fișiere obiecte>
Vom considera exemplul folosit pentru crearea de biblioteci în Linux (main.c
, util.h
, f1.c
, f2.c
):
# obținem fișierul obiect f1.obj din sursa f1.c >cl /c f1.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. f1.c #obținem fișierul f2.obj din sursa f2.c >cl /c f2.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. f2.c >cl /c main.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. main.c #obținem biblioteca statică intro.lib din f1.obj și f2.obj >lib /out:intro.lib f1.obj f2.obj Microsoft (R) Library Manager Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. #intro.lib este compilat împreună cu main.obj pentru a obține main.exe >cl main.obj intro.lib Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:main.exe main.obj intro.lib
Pentru obținerea unei biblioteci statice folosim comanda lib
. Argumentul /out:
precizează numele bibliotecii statice de ieșire. Biblioteca are de obicei extensia *.lib
. Pentru obținerea executabilului se folosește cl
care primește ca argumente fișierele obiect și bibliotecile care conțin funcțiile dorite.
Bibliotecile partajate din Linux au ca echivalent bibliotecile DLL
(Dynamic Link Library) în Windows. Crearea unei biblioteci partajate pe Windows este mai complicată decât pe Linux. Pe de o parte, pentru că în afara bibliotecii partajate (dll
), mai trebuie creată o bibliotecă de import (lib
). Pe de altă parte, legarea bibliotecii partajate presupune exportarea explicită a simbolurilor (funcții, variabile) care vor fi folosite.
Pentru precizarea simbolurilor care vor fi exportate de bibliotecă se folosesc identificatori predefiniți:
Exemplul de mai jos prezintă trei programe: două dintre ele vor fi legate într-o bibliotecă partajată, iar celălalt conține codul de utilizare a funcțiilor exportate.
|
|
Așadar, pentru crearea bibliotecii partajate și utlizarea acesteia de către programul main parcurgem următorii pași:
f1.c
va exporta funcția f1()
folosind __declspec(dllexport)f2.c
va exporta funcția f2()
folosind __declspec(dllexport)main.c
va importa funcțiile f1()
și f2()
folosind __declspec(dllimport)f1.obj
și f2.obj
acestea vor fi folosite la crearea bibliotecii partajate folosind opțiunea /LD
a comenzii cl
.main.obj
cu biblioteca partajată și obținem main.exe
>cl /LD f1.obj f2.obj Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:f1.dll /dll /implib:f1.lib f1.obj f2.obj Creating library f1.lib and object f1.exp >cl main.obj f1.lib Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:main.exe main.obj f1.lib
Alternativ, biblioteca poate fi obținută cu ajutorul comenzii link
:
>link /nologo /dll /out:intro.dll /implib:intro.lib f1.obj f2.obj Creating library intro.lib and object intro.exp >link /nologo /out:main.exe main.obj intro.lib >main.exe Current file name is f1.c Current line 6 in file f2.c
Nmake este utilitarul folosit pentru compilare incrementală pe Windows. Nmake are o sintaxă foarte asemănătoare cu Make. Un exemplu simplu de makefile este cel atașat parser-ului de la tema 1:
OBJ_LIST = parser.tab.obj parser.yy.obj CFLAGS = /nologo /W4 /EHsc /Za EXE_NAMES = CUseParser.exe UseParser.exe DisplayStructure.exe all : $(EXE_NAMES) CUseParser.exe : CUseParser.obj $(OBJ_LIST) $(CPP) $(CFLAGS) /Fe$@ $** UseParser.exe : UseParser.obj $(OBJ_LIST) $(CPP) $(CFLAGS) /Fe$@ $** DisplayStructure.exe : DisplayStructure.obj $(OBJ_LIST) $(CPP) $(CFLAGS) /Fe$@ $** clean : exe_clean obj_clean obj_clean : del *.obj exe_clean : del $(EXE_NAMES)
Nmake oferă următoarele variabile speciale:
Macro | Semnificație |
---|---|
$@ | numele țintei curente |
$* | numele țintei curente mai puțin extensia |
$** | toate dependențele unei ținte |
$? | toate dependențele mai vechi decât ținta |
În rezolvarea laboratorului folosiți arhiva de sarcini lab01-tasks.zip
wget http://ocw.cs.pub.ro/courses/_media/so/laboratoare/resurse/so_win.sh chmod u+x so_win.sh sudo ./so_win.sh
*.sln
corespunzător.
win/VS Tutorial
și dați dublu-click pe fișierul *.sln
pentru a deschide proiectul Visual Studio.Windows PowerShell
)win\VS Tutorial\Debug>"Hello World.exe"
Ok
VS Tutorial/debug.c
la proiectf
astfel încât să întoarcă int
bug
. f
: f
(linia 6), apoi F9bug
în momentul în care apare problema, cât și mesajele afișate în fereastra Outputcmd.exe
).hello
.> cl hello.c
> .\hello.exe
hello_win
.> cl /Fehello_win hello.c
> .\hello_win.exe
hello
.> nmake
> .\hello.exe
main.c
și add.c
. (Hint: type main.c
) main.c
și add.c
.main.exe
din obiectele creat.main.c
și add.c
și executabilul main.exe
(ca la subpunctul precedent)cl /?
pentru a determina cum se definește un macro în linia de comandă.
cl /?
. Căutați opțiunea de definire de macro.win/3-bounds/
bounds.c
, min.c
și max.c
Makefile.static
astfel încât:nmake bounds_static.lib
să se creeze biblioteca statică bounds_static.lib
. Biblioteca va conține fișierele obiect asociate fișierelor min.c
și max.c
bounds_static.exe
obținut din legarea fișierului obiect corespunzător fișierului bounds.c
cu biblioteca bounds_static.lib
win/3-bounds/
Makefile.dynamic
astfel încât:nmake bounds_dynamic.dll
să se creeze biblioteca dinamică bounds_dynamic.dll
. Biblioteca va conține fișierele obiect asociate fișierelor min.c
și max.c
nmake
să se creeze fișierul executabil bounds_dynamic.exe
obținut prin legarea fișierului obiect corespunzător fișierului bounds.c
cu biblioteca bounds_dynamic.dll
__declspec(dllimport)
și __declspec(dllexport)
la antetele funcțiilorcl
pentru a crea biblioteca dinamică, puteți folosi '/Fe<nume>' pentru a seta numele bibliotecii
wget http://ocw.cs.pub.ro/courses/_media/so/laboratoare/resurse/so_lin.sh chmod u+x so_lin.sh sudo ./so_lin.sh
lin/2-lib/
Makefile_static
astfel încât:make libhexdump_static
să creeze biblioteca statică libhexdump_static.a
Biblioteca va conține fișierele obiect asociate fișierelor hexdump.c
și sample.c
make
să creeze executabilul main_static
obținut din legarea fișierului obiect corespunzător lui main.c
cu biblioteca libhexdump_static.a
.lin/2-lib/
Makefile_dynamic
reguli astfel încât:make libhexdump_dynamic
să creeze biblioteca dinamică libhexdump_dynamic.so
. Biblioteca va conține fișierele obiect asociate fișierelor hexdump.c
și sample.c
make
pe lângă biblioteca dinamică libhexdump_dynamic.so
obținută anterior să se creeze și executabilul main_dynamic
obținut din legarea fișierului obiect corespunzător lui main.c
cu biblioteca partajată libhexdump_dynamic.so
.
lin/3-ops/
.ops.c
, mul.c
și add.c
ops.c
, se folosește de funcțiile definite în mul.c
și add.c
pentru a realiza operații de adunare și înmulțire simple. Makefile
, astfel încât:mul.o
, add.o
și ops.o
.ops
din obiectele create.lin/3-ops/
ops.c
definiți simbolul HAVE_MATH
.-D
.ops
math.h
și să legați biblioteca libm
.-l
.lin/4-gdb/
fault.c
Makefile
astfel încât la rularea comenzii make
să se obțină fișierul executabil fault.fault.c
-g
pentru a compila sursa cu simbolurile de debug incluse.print
pentru a printa valorile variabilelor când faceți depanarea.lin/5-linker/
main.c
și str.c
.main
și explicați rezultatele.