Table of Contents

C/SO Tips

Good practice

Verificare cod eroare

Structura unui fișier header

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
 */

Alocare și eliberare de resurse

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.

Folosirea de tool-uri pentru verificarea coding style-ului

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.

Limbaj

Omiterea parantezelor în cazul folosirii macrodefinițiilor

Î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)

main.c
#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

main.c
#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)

main.c
#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

main.c
#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;
}

Duplicarea efectelor secundare în cazul folosirii macrodefinițiilor

Greșit (neadecvat)

main.c
#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

main.c
#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;
}

Nume de variabile/funcții/macrouri care încep cu underscore

Greșit (neadecvat)

test.h
#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

test.h
#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

Includere fișiere C

NU includeți fișiere C în alte fișiere C sau headere. Directiva #include se folosește doar pentru headere.

Greșit (neadecvat)

test.c
int test_fun1(int a, int b)
{
     ...
}
 
int test_fun2(char *c, size_t d)
{
     ...
}
main.c
#include "test.c"
 
int main(void)
{
    ...
    test_fun1(t, u);
    ...
    test_fun2(v, w);
    ...
 
}

Corect

test.c
/* definiții de funcții */
 
int test_fun1(int a, int b)
{
     ...
}
 
int test_fun2(char *c, size_t d)
{
     ...
}
test.h
#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
main.c
#include "test.h"
 
int main(void)
{
    ...
    test_fun1(t, u);
    ...
    test_fun2(v, w);
    ...
}

Alocare dinamică a unui caracter sau întreg

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

Testare condiţie în if sau while

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.

Typecast la / de la void *

Î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;

Operatorul sizeof

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

Comparații între signed și unsigned

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.

Când un array este un pointer

Î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);

Folosirea specificatorului "static" pentru variabile globale și funcții

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:

Utilizare API

Utilizare open

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, ...);

Sfaturi