C Önişlemci Komutları: Makrolar ve Koşullu Derleme Rehberi

C dilinde kaynak kod, doğrudan makine koduna çevrilmez; önce birkaç aşamadan geçen küçük bir yolculuğa çıkar. Önişlemci, bu yolculuğun derlemeden hemen önceki durağıdır. #include, #define ve #if gibi direktifleri işler; başlık dosyalarını ekler, makroları genişletir ve hangi kod parçalarının derleyiciye ulaşacağını belirler. Kısacası önişlemci, sahne açılmadan önce dekoru hazırlayan görünmez bir ekip gibidir. ``

Derlemeden önce neler olur?

Tipik bir C programının üretim süreci kabaca dört aşamaya ayrılır:

Aşama Girdi Yapılan işlem Çıktı
Önişleme .c dosyası Direktifler ve makrolar işlenir Genişletilmiş kaynak kod
Derleme Genişletilmiş kod C kodu assembly diline çevrilir Assembly kodu
Assembly Assembly kodu Makine komutları oluşturulur Nesne dosyası
Bağlama Nesne dosyaları Kütüphaneler birleştirilir Çalıştırılabilir program

Bu süreci sembolik olarak şöyle gösterebiliriz:

\[Kaynak \rightarrow Önişlemci \rightarrow Derleyici \rightarrow Assembler \rightarrow Bağlayıcı\]

GCC kullanırken yalnızca önişlemci çıktısını görmek için şu komut çalıştırılabilir:

gcc -E program.c -o program.i

Oluşan program.i dosyası genellikle oldukça uzundur; çünkü eklenen başlıkların içeriği de bu dosyada görünür.

#include: Başlık dosyalarını eklemek

#include, belirtilen dosyanın içeriğini mevcut kaynak dosyaya metinsel olarak ekler:

#include <stdio.h>
#include "hesaplama.h"
Yazım Genel kullanım Arama davranışı
<stdio.h> Standart veya sistem başlıkları Sistem başlık dizinleri aranır
"hesaplama.h" Projeye ait başlıklar Önce mevcut proje dizini aranır

Aynı başlığın birden fazla kez eklenmesi tanım çakışmalarına yol açabilir. Bu nedenle include guard kullanılır:

#ifndef HESAPLAMA_H
#define HESAPLAMA_H

int topla(int a, int b);

#endif

İlk eklemede HESAPLAMA_H tanımlanır. Sonraki eklemelerde #ifndef koşulu yanlış olacağı için dosyanın gövdesi atlanır.

#define: Metinsel dönüşüm yapan makrolar

Nesne benzeri makrolar sabit değerleri temsil edebilir:

#define PI 3.141592653589793
#define MAKSIMUM_KULLANICI 100

Örneğin dairenin alanı $A = \pi r^2$ bağıntısıyla hesaplanabilir:

#define PI 3.141592653589793
#define KARE(x) ((x) * (x))

double alan(double yaricap) {
    return PI * KARE(yaricap);
}

Parantezler önemlidir. #define KARE(x) x * x yazılsaydı KARE(a + 1) ifadesi a + 1 * a + 1 biçiminde genişleyerek işlem önceliği hatası oluştururdu.

Makroların tür denetimi yapmadığı ve yalnızca metin değiştirdiği unutulmamalıdır. Ayrıca KARE(i++) gibi bir kullanım, argümanı iki kez değerlendirebilir. Bu durumda beklenmeyen yan etkiler ortaya çıkar. Tür güvenliği ve tek değerlendirme gerektiğinde inline fonksiyon genellikle daha iyi bir seçimdir.

Özellik Makro inline fonksiyon
Tür denetimi Yok Var
Argüman değerlendirmesi Birden fazla olabilir Bir kez
Çalıştığı aşama Önişleme Derleme
Hata ayıklama Daha zor Daha kolay

Koşullu derleme

Koşullu direktifler, platforma veya yapılandırmaya göre farklı kodların derlenmesini sağlar:

#ifdef DEBUG
    printf("Hata ayıklama modu etkin\n");
#endif

#if defined(_WIN32)
    printf("Windows üzerinde çalışıyor\n");
#elif defined(__linux__)
    printf("Linux üzerinde çalışıyor\n");
#else
    printf("Bilinmeyen platform\n");
#endif

Komut satırından bir makro tanımlamak da mümkündür:

gcc -DDEBUG program.c -o program

#undef DEBUG ise daha önce tanımlanmış makroyu kaldırır. Önişlemci ifadelerinde defined, mantıksal operatörler ve tamsayı sabitleri kullanılabilir; ancak normal C değişkenleri kullanılamaz. Çünkü değişkenler henüz derleyici tarafından işlenmemiştir.

Sonuç olarak C önişlemcisi güçlü fakat dikkatsiz kullanıldığında şaşırtıcıdır. Makroları küçük ve parantezli tutmak, include guard kullanmak, koşullu derlemeyi belgeli yürütmek ve mümkün olduğunda sabitler ile inline fonksiyonları tercih etmek daha okunabilir, taşınabilir ve güvenli programlar üretir.

c-onislemci-komutlari-51

Yorumlar