Un fișier header reprezintă interfața publică a unui modul. Un fișier header:
struct x_t; struct y_t { struct x_t *x; ... }; /* * nu este necesar să includem header-ul în care este declarat tipul struct x_t * acel header va fi inclus în fișierele sursă care folosesc obiecte de tipul struct x_t */
struct vector; typedef struct vector vector_t; /* * alte module vor folosi tipul vector_t * variabilele de acest tip vor fi manipulate prin funcții exportate de modulul curent */
O practică bună este ca atunci când scriem o instrucțiune ce alocă o resursă (alocare dinamică de memorie, deschidere a unui fișier) să scriem și instrucțiunea asociată care eliberează acea resursă atunci când nu mai este necesară. În acest fel ne asigurăm că nu ținem ocupate resursele sistemului mai mult decât este cazul.
Un coding style bun, folosit consecvent, face mai ușoară și mai rapidă înțelegerea unui cod. Marile proiecte software îsi stabilesc un coding style și realizează utilitare pentru verificarea automată a acestuia. Checkpatch.pl este un script Perl folosit în kernelul Linux pentru a verifica coding style-ului patch-urilor ce urmează a fi submise.
În cazul unei macrodefiniții sub forma unei expresii, aceasta trebuie încadrată între paranteze pentru a evita probleme legate de precendența operatorilor la expandarea macro-ului.
Greșit (neadecvat)
#define add(x, y) x + y int main(void) { float x = 10.0; float y = 20.0; float avg = add(x, y) / 2; // se expandează la: x + y / 2 ... return 0; }
Corect
#define add(x, y) (x + y) int main(void) { float x = 10.0; float y = 20.0; float avg = add(x, y) / 2; // se expandează la: (x + y) / 2 ... return 0; }
Din același motiv, parametrii unei macrodefiniții trebuie încadrați între paranteze.
Greșit (neadecvat)
#define trapezoid_area(h, total_bases_len) (h * total_bases_len / 2) int main(void) { float h = 10.0; float b = 20.0; float B = 30.0; float area = trapezoid_area(h, b + B); // se expandează la: (h * b + B / 2) ... return 0; }
Corect
#define trapezoid_area(h, total_bases_len) ((h) * (total_bases_len) / 2) int main(void) { float h = 10.0; float b = 20.0; float B = 30.0; float area = trapezoid_area(h, b + B); // se expandează la: ((h) * (b + B) / 2) ... return 0; }
Greșit (neadecvat)
#define min(x, y) ((x) <= (y) ? (x) : (y)) int read_int(void) { // citește și întoarce un întreg de la tastatură } int main(void) { int x = 10; int z = min(x, read_int()); // se expandează la: ((x) <= (read_int()) ? (x) : (read_int())) ... return 0; }
În exemplul anterior, dacă primul întreg citit de la tastatură este mai mic decât x atunci funcția read_int() se va apela de două ori, iar variabila z va avea valoarea celui de-al doilea întreg citit.
Corect
#define min(x, y) ((x) <= (y) ? (x) : (y)) int read_int(void) { // citește și întoarce un întreg de la tastatură } int main(void) { int x = 10; int y = read_int(); int z = min(x, y); // se expandează la: ((x) <= (y) ? (x) : (y)) ... return 0; }
Greșit (neadecvat)
#ifndef _TEST_H #define _TEST_H 1 ... #ifndef __TEST_H #define __TEST_H 1 ... #ifndef _TEST_H_ #define _TEST_H_ 1 ... #ifndef __TEST_H__ #define __TEST_H__ 1 ...
Corect
#ifndef TEST_H #define TEST_H 1 /* unele șanse de conflict */ ... #ifndef TEST_H_ #define TEST_H_ 1 ... #ifndef TEST_H__ #define TEST_H__ 1
NU includeți fișiere C în alte fișiere C sau headere. Directiva #include
se folosește doar pentru headere.
Greșit (neadecvat)
int test_fun1(int a, int b) { ... } int test_fun2(char *c, size_t d) { ... }
#include "test.c" int main(void) { ... test_fun1(t, u); ... test_fun2(v, w); ... }
Corect
/* definiții de funcții */ int test_fun1(int a, int b) { ... } int test_fun2(char *c, size_t d) { ... }
#ifndef TEST_H_ #define TEST_H_ 1 /* declarații (antete) de funcții */ int test_fun1(int a, int b); int test_fun2(char *c, size_t d); #endif
#include "test.h" int main(void) { ... test_fun1(t, u); ... test_fun2(v, w); ... }
Greşit(neadecvat)
#define BUFFER_SIZE 128 char *x = malloc(1); char *buffer = malloc(BUFFER_SIZE); if (read(STDIN_FILENO, x, 1) < 0) { ... if (read(STDIN_FILENO, buffer, BUFFER_SIZE) < 0) { ...
Corect
#define BUFFER_SIZE 128 char x; char buffer[BUFFER_SIZE]; if (read(STDIN_FILENO, &x, 1) < 0) { ... if (read(STDIN_FILENO, buffer, BUFFER_SIZE) < 0) { ...
Explicație
x
, se poate utiliza operatorul de referenţiere &
care află adresa unei variabile.buffer
, întrucât se cunoaște din momentul compilării valoarea acesteia, se poate folosi alocarea statică. Alocarea statică este realizată de compilator. Alocarea dinamică presupune gestiunea acesteia de utilizator (gestiunea pointerului, eliberarea zonei, evitarea leak-urilor) însemnând un risc mai ridicat.Greşit
if (fd = open("file", O_RDONLY) < 0) { perror("open"); } else { /* use fd */ }
Corect
if ((fd = open("file", O_RDONLY)) < 0) { perror("open"); } else { /* use fd */ }
Explicaţia
Operatorul <
are prioritate mai mare decât operatorul de atribuire =
. Aceaşi greşeală este şi dacă testăm condiţia fără paranteze într-un while
.
Și mai corect
fd = open("file", O_RDONLY); if (fd < 0) perror("open"); else { /* Use fd. */ }
Formularea cu apelul funcției în condiția if
este error-prone și face codul mai greu de citit.
În C (nu și în C++), nu sunt necesare typecast-uri într-o atribuire atunci când unul dintre termeni este de tip void *
.
Greșit
int *a = (int *) malloc(N * sizeof *a); void *p = (void *) a;
Corect
int *a = malloc(N * sizeof *a); void *p = a;
Dacă dorim să alocăm memorie pentru un array de N numere întregi am putea folosi următoarea instrucțiune:
int *a = malloc(N * sizeof(int));
Dacă decidem să avem numere dintr-un interval mult mai mare, trebuie să modificăm în două locuri:
long long int *a = malloc(N * sizeof(long long int));
Abordarea anterioară este susceptibilă la erori. Este recomandat să folosim operatorul sizeof pentru tipul expresiei *a (expresia nu este evaluată):
long long int *a = malloc(N * sizeof *a);
Operatorul sizeof poate fi folosit pentru a determina numărul de elemente ale unui array alocat static:
int a[100]; printf("%lu\n", sizeof a / sizeof a[0]); // afișează 100
NU comparați tipurile cu semn cu cele fără semn.
Greșit
int a = -1; unsigned int b = 1; if (a < b) printf("a < b\n"); else printf("a >= b\n");
În exemplul anterior, în cadrul expresiei condiționale se face un cast implicit la unsigned int pentru variabila a, rezultând într-o valoare foarte mare (2^32 - 1). Se va afișa incorect că a este mai mare sau egal cu b.
Corect
int a = -1; unsigned int b = 1; if ((INT_MAX < b) || (a < (int)b)) printf("a < b\n"); else printf("a >= b\n");
Explicaţia
Putem face un cast explicit la int pentru variabila b doar dacă avem certitudinea că valoarea sa poate fi reprezentată ca un întreg cu semn. În cazul în care valoarea lui b este mai mare decât constanta INT_MAX (definită în limits.h) atunci cu certitudine a este mai mic decât b.
Înainte de a vedea când un array poate fi considerat un pointer, trebuie să ne reamintim ce este o declarație a unui array. În cazul unui array putem avea trei tipuri de declarații:
extern int a[]; // definit în alt modul ca: int a[N];
int a[10] = {1, 2, 3};
void f(int a[]);
În primele două cazuri, un array NU poate fi rescris ca un pointer. În cazul al treilea, array-ul poate fi rescris ca un pointer la primul element:
void f(int *a);
Compilatorul îl va trata în mod automat ca fiind un pointer, indiferent dacă îl declarăm sau nu ca un array. În mod similar, orice folosire a unui array într-o expresie poate fi rescrisă sub forma unui pointer (iar compilatorul va face asta automat și va trata array-ul ca fiind un pointer la primul element):
int n = a[i]; int m = *(a + i);
O variabilă globală sau o funcție declarată cu specificatorul “static” are internal linkage, adică poate fi referită doar din translation unit-ul respectiv (un translation unit reprezintă un fișier sursă .c împreună cu toate fișierele header pe care acesta le include).
Avantajele acestei practici sunt:
Greşit
if ((fd = open("file_name", O_WRONLY | O_CREAT)) < 0) {
Corect
if ((fd = open("file_name", O_WRONLY | O_CREAT, 0644)) < 0) {
Explicaţia
În cazul în care folosim flagul O_CREAT, trebuie să specificăm drepturile cu care se creează fişierul.
Compilează pentru că funcţia open
este declarată
int open (char *file, int oflag, ...);