12 ponturi pentru programatorii C/C++

(Categoria Limbaje de programare, Autor Alexandru Mosoi)

In urmatoarele cateva randuri am sa incerc sa va arat cateva metode de a scrie.... mai bine. Cea mai mare parte este pentru programatorii C.

Inainte ati putea citi si "Documentation/CodingStyle" aflat in sursa de kernel a Linux-ului. Scuzati-ma daca ma inspir putin. Manualul gcc este si el binevenit.

Pont #0

Prefer C in loc de C++: e mai robust putin ceea ce ma fereste cateodata de greseli. Incercati sa nu folositi un IDE cu debugging inclus (cum ar fi RHIDE sau Borland C++ 3.1). La inceput o sa va vina greu, dar va obisnuiti... si deveniti mai atenti cand scrieti surse. Puteti sa folositi Kate, un editor de text asemanator lui EditPlus de sub Windows. Vim este deasemenea un editor foarte puternic, dar pentru cine stie sa-l foloseasca.

Pont #1

Impartiti programul dumneavoastra in functii, fiecare sa nu depaseasca mai mult de 30-50 de linii (aproximativ 2 ecrane ANSI 80×25). Este important sa aveti mereu o viziune asupra intregii functii. Regula este: complexitatea unei functii trebuie sa fie invers proportionala cu lungimea ei. Puteti sa declarati functiile "inline" (nu pe toate !) pentru a nu pierde din viteza.

Pont #2

Macro-urile nu le recomand. Daca le folositi ca functii aveti grija. Unul dintre colegii mei de la lot a pierdut multe puncte pentru ceva asemanator.

Daca observati, exista cazuri cand query(l, r) era apelata de 2 ori, ceea ce nu se doreste. In schimb, putea sa declare MAX ca o functie inline.

Pont #3

Cand accesati un element din memorie, procesorul citeste de fapt 32 bytes (sau cat de mare e linia de cache, dar o putere a lui 2). Recomand ca structurile voastre sa aiba de asemenea ca dimensiune o putere a lui 2 pentru a nu forta procesorul sa citeasca de 2 ori. O extensie GNU a standardului ANSI C sunt atributele. Pentru structuri, una din cele mai folosite (de mine) este packed ce instruieste compilatorul se nu mai adauge "padding bytes".

Update

Se pare ca pentru ultimele versiuni ale compilatorului aceasta recomandare nu mai este valabila. Structurile de date de dimensiuni 2k au tendinta de a incetini executia programului.
Sursa 1
Sursa 2

Pentru mai multe informatii executati consultati manualul gcc. ("info gcc").

De asemenea, e bine sa nu spargeti aceasta line de cache prea des. Uitati un exemplu:

Pentru 1024 de apelari, pe calculatorul meu, acesta functie consuma cam 18.85s. In schimb, daca as fi scris

... functia s-ar fi executat de ~3 ori mai repede (doar 6.05s) iar rezultatul era acelasi. De ce? pentru ca in primul caz la fiecare accesare a t[j][i] procesorul era nevoit sa acceseze memoria, iar in cazul al doilea cand citea t[i][j], erau citite de fapt si t[i][j+1], t[i][j+2], t[i][j+3]. Si sa nu uitam viteza memoriei este mult mai mica decat cea a procesorului.

Pont #4

Variabilele globale sa nu fie folosite in scop local. Daca as modifica functia astfel

... timpul de executie s-ar fi marit la 6.44s. Nu e prea mult... dar se aduna.

Pont #5

Stack-ul (locul unde se pastreaza toate variabilele locale) este foarte rapid. Modificam acelasi program astfel:

Ignorand faptul ca t nu este initializat (e doar un program de test, nici inainte nu era :D) timpul de executie scade la 1.2s, Wow! Insa aveti grija sa nu o luati pe urmele lui Silviu: sizeof(t) ~= 4Mb care e mult peste limita de 1Mb ce se impune de obicei in concursuri (si asta daca folositi gcc). Cel mai probabil veti primi "Killed by signal 11".

Pont #6

6.a

++i e preferabil in locul lui i ++ (unde nu complica lucrurile).

6.b

Nu va feriti sa folositi "const" si "static". "const" chiar poate sa faca diferenta ca timp si vizibilitate.

6.c

Utilizati si literele mari pentru anumite variabile mai importante (poate si macro-uri).

Pont #7

O alta extensie GNU sunt "zero-length arrays". Se folosesc in general la skiplist-uri pentru a declara un array de dimensiune variabila intr-o structura.

Pont #8

8.a

Folositi-va de utilitarele puse la dispozitie de sistemul de operare (linux in cazul meu). RTFM :)

  • bc - pentru calcule cu numere cu precizie multipla (eg. 21024).
  • octave - pentru calcule matematice mai complicate.
  • gprof - determina cat timp a necesitat executia fiecarei functii sau linii.
  • gcov - determina de cateori a fost apelata o anumita linie.
  • time - pentru aflarea timpului executiei unui program.
  • factor - descompune in factori un numar (eg. factor 666).
  • splint - o versiune free a programului lint: va da foarte multe warning-uri.
  • bash - putin scripting

8.b

Compilati-va sursele cu -W -Wall (tot pentru warning-uri)

8.c

Generatorul de teste si sursa dumneavoastra trebuie sa fie doua programe diferite !

8.d

Pentru debugging folositi fprintf(stderr, ...). Daca se intampla sa uitati, macar nu primiti "wrong answer" din cauza unui printf.

Pont #9

9.a

9.b

Pentru valoarea infinit folosesc o constanta

din mai multe motive:

  • INFI + INFI ramane pozitiv
  • in general e destul de mare

9.c

Daca avem de comparat doua siruri (s1, s2) a caror lungime o stim (len_s1, respectiv len_s2) este mai rapid

decat

9.d

citeste primul caracter dupa spatiile albe (daca exista).

Pont #10

Daca programati in C++ fara sa folositi STL incercati sa renuntati la C++. Unul dintre motive: clasele (implicit iostream: cin, cout, cerr) incetinesc mult executia programului.

Pont #11

In final, o intrebare pentru cei ce folosesc C++ (asta e un hint). Cum se calculeaza factorial la compilare? (fara a scrie efectiv 1*2*3...*n)
Raspuns: Utilizand templaturi. Avem nevoie doar de o constanta N.

PS: nu dau $2.56 pentru fiecare greseala descoperita in acest articol.

remote content