Menu

C'de Koşullu Derleme: #ifdef, #ifndef, #if ve -D

#ifdef, #ifndef, #if, #elif ve #else ile farklı derlemeler için farklı kod derleyin - hata ayıklama anahtarları, platform dalları, komut satırından -D ile makro tanımlama ve kodu devre dışı bırakmak için #if 0.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Koşullu derleme, tek bir kaynak dosyanın birkaç farklı program olmasını sağlar. Ön işlemci, derleyici çalışmadan önce hangi metin bloklarının hayatta kalacağına karar verir - dolayısıyla başarısız bir daldaki kod yalnızca çalışma zamanında atlanmaz, silinir. Hiç ayrıştırılmaz, dolayısıyla çalıştırılabilir dosyaya hiçbir şey katmaz ve hatta mevcut derleyicinin reddedeceği yapılar bile içerebilir.

Sıradan bir if'ten farkı budur. İkisi de yollar arasında seçim yapar; yalnızca biri program var olmadan önce seçer.

#ifdef ve #ifndef

En basit soru, bir makronun tanımlı olup olmadığıdır:

#define DEBUG satırını silin ve her hata ayıklama çıktısı derlemeden yok olur - devre dışı kalmaz, yok olur. Önce yazıldığı hâliyle çalıştırın, sonra o satırı kaldırıp tekrar çalıştırın.

#ifndef bunun olumsuzudur: bloğu yalnızca makro tanımlı değilse tut. En yaygın kullanımı başlık dosyaları sayfasında ele alınan include korumasıdır ve aynı zamanda çağıranın geçersiz kılabileceği bir varsayılan sağlama yoludur:

Her #ifdef ve #ifndef bir #endif ile kapatılmalıdır. Uzun dosyalarda kapatıcıyı not düşmek - #endif /* DEBUG */ - sonradan gerçek zaman kazandırır.

#if, #elif, #else

#if sabit bir tam sayı ifadesi alır ve sıfırdan farklı olduğunda bloğu tutar. Bu, sürüm ve seviye karşılaştırmalarını mümkün kılar:

LOG_LEVEL'ı 0 ya da 3 yapıp tekrar çalıştırın; programın kendisinin biçim değiştirdiğini göreceksiniz.

İfade ön işlemci tarafından değerlendirilir; bu, yalnızca tam sayı sabitlerini, aritmetik ve karşılaştırma operatörlerini ve bunlara genişleyen makroları kullanabileceği anlamına gelir. sizeof'u, enum değerlerini, değişkenleri ya da derleyici gerektiren hiçbir şeyi göremez. Bir #if içindeki tanımsız bir makro hata olmak yerine 0 değerini alır ki bu kullanışlı ve zaman zaman şaşırtıcıdır:

#if FEATURE_X        /* FEATURE_X hiçbir yerde tanımlı değil -> 0 -> blok atılır */

defined(NAME), #ifdef sorusunu bir #if içinde kullanılabilir bir şeye dönüştürür, böylece testleri birleştirebilirsiniz:

#if defined(DEBUG) && !defined(NDEBUG)
    /* hata ayıklama derlemesi ve doğrulamalar açık */
#endif

#if defined(LINUX) || defined(BSD)
    /* iki unix benzeri sistemden biri */
#endif

#if defined(X) ile #ifdef X aynı şeydir; &&, || ya da !'e ihtiyacınız olduğunda ilkine uzanın.

Komut Satırından Makro Tanımlamak

Bu anahtarların asıl gücü, kaynağın hiç değişmesi gerekmemesidir. gcc -D, tüm derleme için bir makro tanımlar:

gcc -DDEBUG program.c -o program        # DEBUG 1 olarak tanımlandı
gcc -DLOG_LEVEL=3 program.c -o program  # belirli bir değer
gcc -DDEBUG -DBUFFER_SIZE=512 a.c b.c -o app

Tek başına -DNAME, #define NAME 1 ile eşdeğerdir. -U NAME bir tanımı kaldırır; bir başlık, kapalı olmasını istediğiniz bir şeyi tanımladığında bu önemlidir.

Yani alışıldık düzen şudur: kaynak #ifndef korumalı varsayılanlar içerir ve derleme komutu bir yapılandırma seçer.

/* config.h */
#ifndef LOG_LEVEL
#define LOG_LEVEL 1        /* varsayılan olarak sessiz */
#endif

#ifndef MAX_CONNECTIONS
#define MAX_CONNECTIONS 64
#endif
# geliştirme derlemesi
gcc -DDEBUG -DLOG_LEVEL=3 -Wall -Wextra -g src/*.c -o app-dev

# yayın derlemesi
gcc -DNDEBUG -O2 src/*.c -o app

NDEBUG standartlaştırılmıştır: onu tanımlamak programdaki her assert()'i devre dışı bırakır, çünkü <assert.h> de koşullu derlemeyle yazılmıştır. Kalıbın küçük hâli budur - derlemenin ne tanımladığına göre farklı şeylere derlenen bir başlık.

Platform Anahtarları

Derleyiciler hedef sistemi tanımlayan makroları önceden tanımlar, böylece tek bir kaynak dosya her sistemde doğru API'yi çağırabilir:

#if defined(_WIN32)
    #include <windows.h>
    #define CLEAR_SCREEN "cls"
#elif defined(__APPLE__)
    #include <unistd.h>
    #define CLEAR_SCREEN "clear"
#elif defined(__linux__)
    #include <unistd.h>
    #define CLEAR_SCREEN "clear"
#else
    #error "Desteklenmeyen platform"
#endif

Yaygın olanlar: _WIN32 (hem 32 hem 64 bit Windows'ta tanımlı), _WIN64, __linux__, __APPLE__, __unix__, __ANDROID__. Derleyici kimliğinin kendi kümesi vardır - __GNUC__, __clang__, _MSC_VER - mimarinin de - __x86_64__, __aarch64__.

Neyin üzerinde derlendiğini bildiren, çalıştırılabilir bir sürüm:

#error tek başına bilmeye değer: derlemeyi sizin mesajınızla durdurur. Bir platform zincirini #else / #error "Desteklenmeyen platform" ile bitirmek, sessiz bir yanlış derlemeyi derleme günlüğünün en tepesinde net bir başarısızlığa dönüştürür.

Derleyicinizin önceden tanımladığı her makroyu listelemek için:

gcc -dM -E - < /dev/null

O çıktı, "bu makinede neyi test edebilirim" sorusunun yetkili cevabıdır.

Kodu Devre Dışı Bırakmak İçin #if 0

Bir bloğu /* ... */ ile yorum satırına almak, blok kendi yorumunu içerdiği anda başarısız olur, çünkü C yorumları iç içe geçmez - içerideki ilk */ dıştaki yorumu bitirir ve ondan sonraki her şey başıboş kod olur. #if 0'ın böyle bir sorunu yoktur:

#if 0
    /* Bu bölgenin tamamı, yorumlarıyla birlikte kaldırılır. */
    legacy_init();
    int n = old_calculation(42);  /* bu yorum bile sorun değil */
    report(n);
#endif

Kodu geri getirmek için #if 1 yapın. Editörler onu C olarak vurgulamaya devam eder ve bir #if 0'ı başka bir #if içine yerleştirebilirsiniz.

Bu bir hata ayıklama aracıdır, bir depolama sistemi değil. #if 0 içinde duran kod hiç derlenmez, dolayısıyla sessizce çürür - biri anahtarı çevirdiğinde artık derlenmeyecektir. Bir sorunu ikiye bölerek ararken kullanın, sonra bloğu silin ve hatırlamayı sürüm denetimine bırakın.

Koşullu Derleme Nerede Ters Gider

Üç başarısızlık biçimi acının çoğunu açıklar.

Hiç derlenmeyen kod hiç denetlenmez. Etkin olmayan bir #ifdef dalındaki bir yazım hatası, biri o yapılandırmayı derleyene dek görünmezdir - belki aylar sonra, belki sizde olmayan bir platformda CI'da. Bir projenin ilginç platform dalları varsa hepsini düzenli olarak derleyin.

#ifdef'i denetim akışıyla iç içe geçirmek hızla okunmaz hâle gelir. Bu tür bir şey üzerine akıl yürütmek zordur:

if (ready) {
#ifdef FAST_PATH
    fast_send(buf);
} else {
#endif
    slow_send(buf);
}

Bir dalda açılıp başka bir dalda kapatılan süslü parantezler geçerli ve berbattır. Tüm fonksiyonları saran koşulları tercih edin ve aralarında seçim yapın:

#ifdef FAST_PATH
static void send_data(const char *buf) { fast_send(buf); }
#else
static void send_data(const char *buf) { slow_send(buf); }
#endif

Çalışma zamanı if'i çoğu zaman daha iyidir. Her iki dal da her yerde derlenecekse, sıradan bir if (debug_enabled) her iki yolu da tür denetimli, test edilebilir ve yeniden derlemeden anahtarlanabilir tutar. Ön işlemciyi yalnızca onun yapabileceği şeye ayırın: diğer platformda gerçekten derlenemeyen kod.

Bunların hepsini birleştiren kalıp için - varsayılanlardan oluşan bir başlık, derleme başına bir -D ve include korumalarına sarılmış platform dalları - bkz. başlık dosyaları; #define'ın kendisi için makrolar.

Sıkça Sorulan Sorular

C'de koşullu derleme nedir?

Derleyici çalışmadan önce kaynak kod bloklarını tutmak ya da atmak için ön işlemci yönergeleri kullanmaktır. Başarısız bir #ifdef içindeki kod metinden tamamen silinir - hiç ayrıştırılmaz, hiç derlenmez ve çalıştırılabilir dosyada hiç görünmez. Tek bir kaynak dosyanın birden fazla platformu ya da derleme türünü desteklemesi böyle olur.

#ifdef ile #if arasındaki fark nedir?

#ifdef NAME yalnızca makronun var olup olmadığını sorar, değeri ne olursa olsun. #if ifade sabit bir tam sayı ifadesini değerlendirir, dolayısıyla değerleri karşılaştırabilir: #if VERSION >= 3. Yalnızca var ya da yok olan bayraklar için #ifdef, değer önemliyse #if kullanın. #if defined(NAME) ikisini birleştirir ve && ile || ile birleştirilebilir.

gcc ile komut satırından nasıl makro tanımlarım?

-D kullanın: gcc -DDEBUG program.c, dosya #define DEBUG 1 ile başlıyormuş gibi DEBUG'ı tanımlar ve -DMAX=50 ona belirli bir değer verir. Derleme sistemleri kaynağı düzenlemeden özellikleri böyle açar ve -U NAME bir tanımı kaldırır.

Kodu yorum satırına almak yerine neden #if 0 kullanmalı?

Çünkü /* ... */ yorumları iç içe geçmez - bölgenin içindeki ilk */ yorumu erken bitirir ve gerisi bozuk kod olur. #if 0 ... #endif, içinde kaç yorum olursa olsun her miktarda kodu kaldırır, söz dizimi vurgulamasını canlı tutar ve #if 1'e çevirmek çok kolaydır.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA