Menu

C Ön İşlemcisi: #include, #define ve Derlemeden Önce Çalışanlar

Ön işlemci, derleyici görmeden önce kaynak metninizi düzenler - başlıkları yapıştırır, makroları yerleştirir ve kodu açıp kapatır. İşte tüm yönerge ailesi ve çıktısına nasıl bakacağınız.

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

Şimdiye kadar derlediğiniz her C dosyası #include <stdio.h> gibi bir satırla başladı ve o satır C değildir. Ayrı bir programa - önce çalışan, kaynak metninizi yeniden yazan ve sonucu derleyiciye veren ön işlemciye - bir talimattır.

O iki aşamalı bölünmeyi anlamak C'nin davranışının çoğunu açıklar: başlıkların neden içe aktarılmak yerine yapıştırıldığını, bozuk bir makronun neden gayet düzgün görünen bir satırda hata ürettiğini ve aynı kaynak dosyanın neden farklı sistemlerde farklı programlara derlenebildiğini.

Derlemeden Önce Gerçekte Ne Olur

Bir .c dosyasını derlemek tek adım değildir. Kabaca dörttür:

  1. Ön işleme - # ile başlayan her satıra uy ve genişletilmiş büyük tek bir kaynak metin (çeviri birimi) üret.
  2. Derleme - o metni assembly'ye, sonra bir nesne dosyasına çevir.
  3. Assembly'ye çevirme - makine kodu üret.
  4. Bağlama - nesne dosyalarını kütüphanelerle birleştirip çalıştırılabilir bir dosya oluştur.

Ön işlemci yalnızca metin üzerinde çalışır. Bir değişkenin ne olduğunu, bir tipin ne olduğunu ya da süslü parantezlerinizin dengeli olup olmadığını bilmez. Karakterleri ve belirteçleri görür, bazılarını başkalarıyla değiştirir ve yoluna devam eder. Makrolarla ilgili tuhaf olan her şey o tek olgudan çıkar.

Derleyici bu programı okuduğunda hiçbir yerde GREETING yoktur. Ön işlemci onu, siz yazmışsınız gibi, "Bir makrodan merhaba" sabitiyle çoktan değiştirmiştir.

Yönerge Ailesi

Her ön işlemci yönergesi, satırındaki ilk boşluk olmayan karakter olarak # ile başlar. Noktalı virgül yoktur ve ters eğik çizgiyle devam ettirmedikçe yönerge satırın sonunda biter.

YönergeNe yapar
#includeBaşka bir dosyanın içeriğini yapıştırır
#defineBir makro (metin değiştirme) tanımlar
#undefBir makro tanımını kaldırır
#ifdef, #ifndefSonraki kodu yalnızca bir makro tanımlı(değil)se tutar
#if, #elif, #else, #endifKodu sabit bir ifadeye göre tutar
#errorDerlemeyi bir mesajla durdurur
#pragma#pragma once gibi derleyiciye özgü talimat
#lineBildirilen satır numarasını değiştirir (nadir)

Bunlardan üçünün kendi sayfaları var: makrolar #define'ı derinlemesine, koşullu derleme #if ailesini ve başlık dosyaları #include'un çok dosyalı bir programı yapılandırmak için nasıl kullanıldığını ele alıyor.

#include: Açılı Parantez ile Tırnak

#include tam olarak tek bir şey yapar: kendi satırını, adı verilen dosyanın tüm içeriğiyle değiştirir. Dahil edilen o dosya da ön işlemden geçer, dolayısıyla onun kendi #include satırları da genişler.

#include <stdio.h>     /* sistem include dizinlerini ara */
#include "config.h"    /* önce bu dosyanın dizinini ara */

Fark arama sırasıdır:

  • <açılı parantezler> derleyicinin standart include dizinlerine bakar - /usr/include, araç zincirinin kendi klasörleri ve -I ile eklediğiniz her şey. Bu, kütüphane başlıkları içindir.
  • "tırnaklar" önce dahil etme işlemini yapan dosyayı içeren dizine bakar, sonra açılı parantezlerle aynı listeye döner. Bu, sizin yazdığınız başlıklar içindir.

Çoğu derleyicide her iki biçim de her iki başlık türü için çalışır, ama gelenek anlam taşır: açılı parantezler "bu başkasının başlığı", tırnaklar "bu benim" der. Onları karıştırmak, bir projenin kendi başlığı yerine bayat bir sistem başlığını dahil etmesine yol açar.

Dahil etme metinsel yapıştırma olduğu için aynı başlığı iki kez dahil etmek onu iki kez yapıştırır - başlıkların include korumalarına ihtiyaç duymasının sebebi budur. Başlık dosyaları sayfasının düzelttiği ilk şey odur.

#define: Metin Değiştirme, Başka Bir Şey Değil

#define NAME degistirme, ön işlemciye şunu söyler: buradan itibaren NAME belirteci nerede görünürse yerine degistirme'yi koy.

Burada olmayan şeye dikkat edin. MAX_USERS'ın türü yoktur. Hiçbir yerde saklanmaz. Bir hata ayıklayıcıda incelenemez. Bir bul-değiştir kuralıdır ve ön işlemeden sonra program harfi harfine printf("%s, %d kullaniciya izin veriyor\n", "Coddy", 100); içerir.

Bu ayrıca değiştirmenin kör olduğu anlamına gelir. Bu derlenir ve şaşırtıcı bir şey yapar:

#define SIZE 5 + 1

int arr[SIZE];          /* sorunsuz: int arr[5 + 1]; */
int total = SIZE * 2;   /* 5 + 1 * 2 == 7, 12 değil */

Çözüm - her şeyin etrafına parantez - argüman alan makrolarla birlikte makrolar sayfasının merkezî kuralıdır.

Değiştirme metni olmayan bir #define, ismi "var ama boş" olarak tanımlar. Bu, bir değiştirme olarak işe yaramaz ve bir bayrak olarak vazgeçilmezdir:

#define DEBUG          /* tanımlı, hiçbir şeye genişler */

Kod o zaman #ifdef ile DEBUG'ın var olup olmadığını sorabilir.

Ön İşlemcinin Çıktısını Görmek

Sezgi geliştirmenin en iyi yolu, ön işlemcinin gerçekte ne ürettiğine bakmaktır. gcc -E, ön işlemeden sonra durur ve sonucu yazdırır:

gcc -E hello.c

<stdio.h> içeren bir dosya için bu, sisteminize göre 700 ila 30.000 satırdır - neredeyse hepsi başlığın kendi içeriğidir. Yalnızca kendi kısmınızı görmek için sonunu alın:

gcc -E hello.c | tail -20

Bunun gibi bir dosyada deneyin:

#define SQUARE(x) ((x) * (x))
#define LIMIT 10

int main(void) {
    int n = SQUARE(LIMIT);
    return n;
}

Çıktının sonu şunu gösterir:

int main(void) {
    int n = ((10) * (10));
    return n;
}

Her makro gitmiştir; yalnızca yerleştirilmiş metin kalır. Bir makro yanlış davrandığında bu komut size sebebini saniyeler içinde söyler ve tahmin etmekten iyidir. İki yararlı yol arkadaşı: gcc -dM -E - < /dev/null, derleyicinizin önceden tanımladığı her makroyu listeler ve gcc -E -P file.c satır işaretçisi gürültüsünü atlar.

Hatalar Neden Yanlış Satırı Gösterir

Derleyici genişletilmiş metni gördüğü için, bir makronun içindeki bir hata, yazıldığı yerde değil makronun kullanıldığı yerde bildirilir:

#define HALF(x) (x / 2

int main(void) {
    int y = HALF(8);   /* hata burada bildirilir */
    return 0;
}

Eksik parantez #define satırındadır, ama derleyici HALF(8)'i içeren satırdan şikâyet eder; çoğu zaman bağlamda hiçbir anlam ifade etmeyen, beklenmeyen bir belirteçle ilgili bir mesajla. Bir hata imkânsız göründüğünde dosyayı gcc -E ile genişletin ve gerçek satırı okuyun.

Modern derleyiciler yardım eder: GCC ve clang, tanıma geri işaret eden "in expansion of macro" notu yazdırır. O notları gerçekten görmek için -Wall -Wextra ile derleyin.

Önceden Tanımlı Makrolar

Ön işlemci bazı makroları kendisi sağlar. Bunlar tanı için gerçekten yararlıdır:

__FILE__ ve __LINE__, mevcut dosya adına ve satır numarasına genişler; doğrulama ve günlükleme makroları bir şeyin nerede ters gittiğini böyle bildirir. (__func__ biraz farklıdır - bir ön işlemci makrosu değil, derleyicinin sağladığı gerçek bir tanımlayıcıdır, ama aynı şekilde kullanılır.)

Derleyiciler ayrıca __linux__, _WIN32 ve __APPLE__ gibi platform makrolarını önceden tanımlar. Sistem başına farklılaşması gereken kod bunları #ifdef ile test eder ki bu koşullu derlemenin konusudur.

Çıkarılacak Ders

Ön işlemci küçüktür ve aptaldır, ikisi de bilerektir. Size üç güç verir - bir dosyayı çekmek, metin yerleştirmek, kodu açıp kapatmak - ve bunlarla birlikte hiçbir tür denetimi vermez.

Bu takas, standart tavsiyenin önce dil özelliklerine uzanmak olmasının sebebidir: sabitler için yapabildiğinizde #define yerine const int ya da bir enum, fonksiyon benzeri bir makro yerine gerçek bir fonksiyon kullanın. Ön işlemcinin gerçekten doğru araç olduğu yerde - başlıklar, taşınabilirlik anahtarları, derleme zamanı yapılandırması - yeri doldurulamaz.

Sıradaki: makrolar, #define'ı ciddiye alıyor: argümanlar, parantez kuralları ve yazmadığınız bir koda metin yerleştirmenin getirdiği tuzaklar.

Sıkça Sorulan Sorular

C'de ön işlemci nedir?

Derlemeden önce çalışan bir metin işleme adımıdır. # ile başlayan satırlara uyar - başlık dosyalarının içeriğini #include'un yerine yapıştırır, #define ile tanımlanmış makroları yerleştirir ve #if/#ifdef'e göre kodu siler ya da tutar. Derleyici yalnızca sonucu görür, asıl dosyanızı asla görmez.

#include <stdio.h> ile #include "myfile.h" arasındaki fark nedir?

Açılı parantezler, standart kütüphane başlıklarının yaşadığı derleyicinin sistem include dizinlerini arar. Tırnaklar önce mevcut dosyanın dizinini arar, sonra sistem yollarına döner. Kütüphane başlıkları için açılı parantez, kendi yazdığınız başlıklar için tırnak kullanın.

Ön işlemcinin ne ürettiğini nasıl görebilirim?

Ön işlemeden sonra durup genişletilmiş kaynağı yazdırmak için gcc -E file.c çalıştırın. <stdio.h> içeren bir dosyada çıktı binlerce satırdır, dolayısıyla borulayın: gcc -E file.c | tail -30, her makrosu çoktan yerleştirilmiş kendi kodunuzu gösterir.

#include bir C deyimi midir?

Hayır. Yönergeler asıl C dilinin parçası değildir - kendi satır tabanlı söz dizimleri vardır, noktalı virgül almazlar ve derleyici programınızı ayrıştırdığında çoktan gitmişlerdir. Bir makrodaki hatanın düzgün görünen bir satırda kafa karıştırıcı bir hata olarak görünmesinin sebebi budur.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA