#if, #ifdef, #ifndef în C

Compilare condiționată: preprocesorul păstrează sau elimină bucăți de cod în funcție de macro definite sau de valoarea lor.

Sintaxă
#if expresie
#elif expresie
#else
#endif

#ifdef NUME      // la fel ca #if defined(NUME)
#ifndef NUME
#elifdef NUME    // C23
#elifndef NUME   // C23
#error "mesaj"
#warning "mesaj"  // standard din C23

Ce face #if, #ifdef, #ifndef

Directivele #if, #ifdef și #ifndef aleg ce cod ajunge la compilator. Codul dintr-o ramură falsă este eliminat înainte de compilare, ca și cum nu ar fi fost scris. Așa se fac versiunile de depanare (#ifdef DEBUG), codul diferit pentru Windows și Linux și gărzile de includere.

#if evaluează o expresie cu numere întregi și macro. defined(NUME) este 1 dacă macro-ul există. #ifdef NUME este prescurtarea pentru #if defined(NUME), iar #ifndef pentru opusul ei. C23 adaugă #elifdef și #elifndef, plus __has_include(<antet>), care verifică dacă un antet există.

#error "mesaj" oprește compilarea cu mesajul dat, util când lipsește o configurare obligatorie. #warning afișează doar un avertisment; era o extensie a compilatoarelor și a devenit standard în C23.

Parametri

Parametru Descriere
defined(NUME) 1 dacă macro-ul NUME este definit, 0 altfel. Se poate combina: #if defined(A) && !defined(B).
__has_include(...) C23: 1 dacă antetul poate fi inclus. Exemplu: #if __has_include(<stdckdint.h>).

Exemple

Exemplul 1: Mesaje de depanare doar când este definit DEBUG

Aici DEBUG este definit în cod. În practică se lasă afară și se pornește din linia de comandă cu clang -DDEBUG program.c. Fără el, linia cu „[debug]” nici nu ajunge în program.

#include <stdio.h>

#define DEBUG
#define NIVEL 2

int main(void) {
    int note[] = {9, 7, 10};
    int suma = 0;
    for (int i = 0; i < 3; i++) {
        suma += note[i];
#ifdef DEBUG
        printf("[debug] după nota %d, suma = %d\n", i + 1, suma);
#endif
    }
#if NIVEL >= 2
    printf("Nivel detaliat: %d note\n", 3);
#endif
    printf("Media: %.2f\n", suma / 3.0);
    return 0;
}

Rezultat

[debug] după nota 1, suma = 9
[debug] după nota 2, suma = 16
[debug] după nota 3, suma = 26
Nivel detaliat: 3 note
Media: 8.67

Exemplul 2: Cod diferit pentru fiecare sistem de operare

Compilatorul definește singur macro precum _WIN32, __linux__ sau __APPLE__. Exemplul a fost compilat pe macOS, deci a rămas ramura #elifdef __APPLE__ (C23). __has_include verifică dacă antetul C23 <stdckdint.h> există.

#include <stdio.h>

#ifdef _WIN32
#define SISTEM "Windows"
#elifdef __linux__
#define SISTEM "Linux"
#elifdef __APPLE__
#define SISTEM "macOS"
#else
#define SISTEM "alt sistem"
#endif

#if __has_include(<stdckdint.h>)
#define ARE_CKDINT 1
#else
#define ARE_CKDINT 0
#endif

int main(void) {
    printf("Compilat pentru: %s\n", SISTEM);
    printf("Are <stdckdint.h>: %s\n", ARE_CKDINT ? "da" : "nu");
    return 0;
}

Rezultat

Compilat pentru: macOS
Are <stdckdint.h>: da

Exemplul 3: Oprirea compilării cu #error

Dacă versiunea configurată este prea mică, #error oprește compilarea cu mesajul tău. Programul nu se construiește deloc.

#define VERSIUNE_CONFIG 2

#if VERSIUNE_CONFIG < 3
#error "Ai nevoie de versiunea 3 a configurației sau mai nouă"
#endif

int main(void) {
    return 0;
}

Mesajul compilatorului (programul nu se compilează)

exemplu.c:4:2: error: "Ai nevoie de versiunea 3 a configurației sau mai nouă"
    4 | #error "Ai nevoie de versiunea 3 a configurației sau mai nouă"
      |  ^
1 error generated.

Greșeli frecvente

  • Uiți #endif. Compilatorul raportează „unterminated conditional directive” la linia cu #if, nu acolo unde lipsește #endif.
  • Scrii #if DEBUG când DEBUG a fost definit fără valoare (#define DEBUG). Expresia devine goală și compilarea dă eroare. Pentru verificarea existenței folosește #ifdef DEBUG sau #if defined(DEBUG).
  • Folosești #if cu variabile din program (#if nota > 5). Preprocesorul rulează înainte de compilare și nu știe nimic despre variabile; un nume necunoscut valorează 0.

Comenzi înrudite

  • #define: Definește o macro: un nume pe care preprocesorul îl înlocuiește cu textul dat, înainte ca programul să fie compilat.
  • #include: Copiază conținutul unui fișier antet (.h) în locul liniei, ca funcțiile și tipurile declarate acolo să poată fi folosite.
  • Macro cu parametri: Macro care primesc argumente, ca o funcție: textul lor este copiat în locul apelului, cu operatorii # și ## și argumente variabile.
  • static_assert: Verifică o condiție la compilare: dacă este falsă, programul nu se compilează și apare mesajul tău.
  • Compilarea cu gcc și clang: Cum transformi codul C într-un program executabil cu gcc sau clang și ce fac opțiunile folosite cel mai des.