This is an old revision of the document!
gcc
) și Windows (folosind cl
)Dezvoltarea trebuie făcută exclusiv pe mașinile virtuale SO.
git
. Indicați în README link-ul către repository dacă ați folosit git.
Asigurați-vă că responsabilii de teme au drepturi de citire asupra repo-ului vostru.
Ca să vă creați un repo de gitlab în instanța facultății: în repository-ul so-assignments de pe Github se află un script Bash care vă ajută să vă creați un repository privat pe instanța de Gitlab a facultății, unde aveți la dispoziție 5 repository-uri private utile pentru teme. Urmăriți indicațiile din README și de pe wiki-ul SO.
Motivul pentru care încurajăm acest lucru este că responsabilii de teme se pot uita mai rapid pe Gitlab la temele voastre pentru a vă ajuta în cazul în care întâmpinați probleme/bug-uri. Este mai ușor să primiți suport în rezolvarea problemelor implementării voastre dacă le oferiți responsabililor de teme acces la codul sursă pe Gitlab.
Crash-course practic de git puteți găsi aici: git-immersion
student@so:~$ git clone https://github.com/systems-cs-pub-ro/so-assignments.git student@so:~$ cd so-assignments/1-multi
În repository-ul de pe Github se vor găsi și scheletele pentru temele viitoare, care vor fi actualizate și se vor putea descărca pe viitor folosind comanda:
student@so:~$ git pull
Tot prin comanda de mai sus se pot obține toate actualizările făcute în cadrul temei 1.
Să se implementeze în C un mini preprocesor pentru fișiere conținând cod sursă C. Preprocesarea este o etapă premergătoare compilării efective a fișierului ce conține cod sursă. Preprocesorul va analiza fișierul de intrare, conținând cod sursa C și va scrie, la consolă sau într-un fișier de iesire, rezultatul preprocesării fișierelor de intrare.
Rezolvarea temei presupune implementarea unui subset al directivelor de preprocesare specifice limbajului C: #define
, #include
, #if
, #elseif
(sub forma #elif
), #else
, #endif
, #ifdef
, #ifndef
, #undef
. Sintaxa și descrierea acestora sunt prezentate în tabelul de mai jos.
Directiva | Descrierea directivei |
---|---|
#define <SYMBOL> <MAPPING> | Stochează o asociere între <SYMBOL> și <MAPPING> . Toate aparițiile lui <SYMBOL> în fișierul cu codul sursă vor fi înlocuite cu <MAPPING> (vezi exemplul de mai jos). |
#if <COND> / #elif <COND> / #else / #endif | Se verifică secvențial dacă <COND> se evaluează la un literal întreg diferit de 0 . În caz afirmativ, în fișierul rezultat se vor procesa și adauga doar liniile de cod specifice primului bloc a cărui condiție a fost validată. |
#ifdef <SYMBOL> / #ifndef <SYMBOL> / #else / #endif | Se verifică dacă <SYMBOL> a fost sau nu definit anterior. |
#include “HEADER” | Realizeaza preprocesarea fisierului indicat de “HEADER” si adauga liniile de cod preprocesat in fisierul de iesire. |
Executabilul rezultat se va numi so-cpp
și va avea următoarea semnătură:
so-cpp [-D <SYMBOL>[=<MAPPING>]] [-I <DIR>] [<INFILE>] [ [-o] <OUTFILE>]
Semnificația argumentelor este următoarea:
-D <SYMBOL>[=<MAPPING>]
sau -D<SYMBOL>[=<MAPPING>]
: va defini simbolul cu numele <SYMBOL>
și valoarea <MAPPING>
; dacă <MAPPING>
lipsește, <SYMBOL>
va primi valoarea șirului vid (””
). <SYMBOL>
poate fi lipit de -D
sau nu.-I <DIR>
sau -I<DIR>
: va adăuga un director în care se vor căuta fișiere incluse de codul sursă folosind directive #include
-o <OUTFILE>
sau -o<OUTFILE>
: va scrie output-ul preprocesat în fișierul <OUTFILE>
<INFILE>
: specifică un fișier din care se va citi codul sursă pentru; dacă parametrul lipsește, codul sursă va fi obținut de la consolă (stdin
)
Atenție: argumentele specificate cu modificatorii -D
și -I
pot fi folosiți de mai multe ori; de fiecare dată vor adăuga o nouă definiție, respectiv vor apenda un nou path. Fișierele de intrare și de ieșire pot fi definite o singură dată! Pentru mai multe detalii, puteți consulta pagina de manual a preprocesorului C.
Exemplu de fișier de intrare, conținând cod sursă C:
#define VAR0 1 int main(int argc, char **argv) { int y = VAR0 + 1; printf("VAR0 = %d\n", VAR0); return 0; }
În urma preproceseării se va obține fișierul:
int main(int argc, char **argv) { int y = 1 + 1; printf("VAR0 = %d\n", 1); return 0; }
Se observă că în exemplul de mai sus, nu toate aparițiile șirului de caractere VAR0
au fost înlocuite cu 1
: aparițiile într-un context de literal șir de caractere ale unui simbol introdus prin directiva #define
nu trebuie înlocuite.
În vederea obținerii functionalității specifice unui preprocesor, se recomandă implementarea unei structuri de date de tip HashMap. Aceasta va fi folosită pentru a stoca asocieri de tipul <SYMBOL, MAPPING>
.
În urma preprocesării fișierului de intrare, trebuie ca într-o structură de date de
tip HashMap să existe o singură asociere între două șiruri de caractere, anume <“VAR0”, “1”>
.
#define
trebuie implementată astfel încât să suporte:#define
-uri simple (precum cel din exemplu)#define
-uri care folosesc în componența lor alte #define
-uri#define
-uri de tip multilinie (respectând aceeași sintaxa ca în C)#define
-uri parametrizate (de tip funcție)#if <COND>
/ #elif <COND>
, nu este necesar să existe suport pentru operații aritmetice, de tipul #if 2 + 3
. Însa, este necesar sa existe suport pentru utilizarea altor #define
-uri pe post de <COND>
.#include
va suporta doar includerea de fisiere header oferite in scheletul temei. Astfel, nu trebuie implementat suport pentru includerea fișierelor din sistem de tipul #include <stdio.h>
sau #include <stdlib.h>
#include
trebuiesc căutate în directorul în care se află fișierul de input, sau în directorul curent, în cazul în care codul de input este specificat la consolă. Dacă nu este găsit în directorul fișierului, el este căutat în toate directoarele specificate folosind parametrul -I
, în ordinea în care acestea au fost specificate în linia de comandă. \t[]{}<>=+-*/%!&|^.,:;()\
.so-cpp
pe Linux și so-cpp.exe
pe Windows.malloc/calloc/realloc
(în funcție de implementarea aleasă). În cazul în care una dintre aceste funcții eșuează, trebuie întors codul de eroare 12
(este codul de eroare pentru ENOMEM
). Acest cod de eroare trebuie propagat și returnat până la ieșirea din program. Valoarea erorii este pozitivă.main
se apelează f1
, iar f1
apelează f2
: dacă eroarea apare în momentul apelului unui malloc
în funcția f2
, atunci codul de eroare (valorea 12
) va fi întors în f1
, din f1
va trebui întors tot 12
, iar din main se va ieși cu același cod de eroare.system
, exec
, sau orice altă metodă) pentru a implementa functionalitatea cerută.Arhiva temei va fi încărcată de două ori pe vmchecker (Linux și Windows). Arhiva trimisă trebuie să fie aceeași pe ambele platforme (se vor compara cele două arhive trimise).
md5sum
sau sha1sum
(sau comenzi similare) asupra arhivelor voastre dacă ați dezvoltat în locuri diferite.
Arhivele trebuie să conțină sursele temei, README
și două fișiere Makefile care conțin target-urile build
și clean
:
GNUmakefile
.m
mic).Makefile
.build
trebuie să fie cea principală (executată atunci când se dă make
fără parametrii)
Executabilul rezultat din operația de compilare și linking se va numi so-cpp
pe Linux și so-cpp.exe
pe Windows.
#ifdef __linux__ [...] #ifdef _WIN32 [...]
GNUmakefile
pentru Linux și Makefile
pentru Windows, cum e precizat mai sus) iar checker-ul va ști, în funcție de sistemul lui de operare, ce makefile să folosească.
Nota mai poate fi modificată prin depunctări suplimentare:
-2
implementare netransparentă a structurii de date de tip HashMap; HashMap-ul ar trebui să fie abstractizat cu un singur obiect (în C: structură de date), iar operațiile pe HashMap trebuie făcute pe obiectul respectiv. Puteți folosi definiții proprii pentru elementele HashMap-ului.-4
alocare statică HashMap
Una dintre depunctări este pentru leak-uri de memorie. În Linux pentru identificarea lor puteți folosi utilitarul valgrind.
Pentru instalarea gdb
și valgrind
, pe o distribuție Ubuntu se poate folosi comanda:
student@so:~$ sudo apt-get install gdb valgrind
Pentru debugging și detectarea leak-urilor de memorie este necesar să ștergeți toate optimizările de la flag-urile de compilare (e.g. -O3
) și trebuie să compilați doar cu flag-urile -Wall -g
(sau cele care mai activează alte warning-uri, e.g. -Wextra
).
Nu trebuie la fiecare eroare considerată fatală să eliberați fiecare pointer alocat dinamic. În cadrul corecturii temei principala verificare pentru memory leaks va fi pe o funcționare corecta/normală, fără input invalid. Rețineți că memory leak-ul apare atunci când programul vostru nu poate returna sistemului de operare memoria folosită! Concentrați-vă pe folosirea valgrind
pe teste care trec și care dau input valid într-o primă fază.
Makefile-ul trebuie să respecte următoarea structură: pentru fiecare fișier .c
generat trebuie să se obțină un fișier obiect. La final trebuie să faceți linkarea între sursa principală (să zicem main.c
din care se obține main.o
) și celelalte fișiere obiect obținute din celelalte surse ale voastre.
Porniți de la exemplele de Makefile atât pentru Linux cât și pentru Windows oferite în laboratorul 1. Un alt exemplu puteți găsi aici.
Denumirile lor trebuie să fie:
GNUmakefile
.m
mic).Makefile
.Pentru a clona repo-ul și a accesa resursele temei 1:
student@so-vm:~$ git clone https://github.com/systems-cs-pub-ro/so-assignments.git student@so-vm:~$ cd so-assignments student@so-vm:~/so-assignments$ cd 1-multi
#define
?ifdef __linux__
)freopen
?fopen
, fread
, fwrite
, fclose
.realloc
?fgets
, fscanf
, printf
, fprintf
?gets
!cl.exe
nu mi se compilează același cod care mi se compila pe Linux. De ce?C99
ca standard la gcc
, care printre altele acceptă să declari variabile în mijlocul codului. Pe Windows, compilatorul cl
folosește standardul C89
, care forțează declararea variabilelor doar la început (un exemplu de problema). student@so:~$ wget https://raw.githubusercontent.com/torvalds/linux/master/scripts/checkpatch.pl student@so:~$ export PATH=$PATH:/path/to/dir/with-checkpatch student@so:~$ cd /path/to/lin/checker && ./run_all.sh
Pentru întrebări sau nelămuriri legate de temă folosiți lista de discuții sau canalul de IRC.
[Tema1][Platforma] Titlul problemei
. Exemple de așa da:
Exemple de așa nu:
Evident, și în cel de-al doilea caz veți primi răspunsuri, dar e posibil să le primiți mai greu. În conținutul emailului, în caz de probleme mai specifice dați cât mai multe detalii despre ce ați încercat, mesaje de eroare, faceți attach la log-uri de execuție, output-uri de comenzi respectiv ce comenzi ați rulat etc.
Revedeți și secțiunea de guidelines pentru lista de discuții SO