21 Haziran 2020 Pazar

C programlama ön işlemci kavramları

ÖNİŞLEMCİ KAVRAMI
Şimdiye kadar tek bir yapı halinde ele aldığımız C derleyicileri aslında, iki ayrı modülden
oluşmaktadır:
1. Önişlemci Modülü
2. Derleme Modülü
İsim benzerliği olsa da önişlemcinin bilgisayarın işlemcisi ya da başka bir donanımsal
elemanıyla hiçbir ilgisi yoktur, önişlemci tamamen C derleyicisine ilişkin yazılımsal bir
kavramdır.
Önişlemci, kaynak program üzerinde birtakım düzenlemeler ve değişiklikler yapan bir ön
programdır. Önişlemci programının bir girdisi bir de çıktısı vardır. Önişlemcinin girdisi kaynak
programın kendisidir. Önişlemcinin çıktısı ise derleme modülünün girdisini oluşturur. Yani
kaynak program ilk aşamada önişlemci tarafından ele alınır. Önişlemci modülü kaynak
programda çeşitli düzenlemeler ve değişiklikler yapar, daha sonra değiştirilmiş ve düzenlenmiş
olan bu kaynak program derleme modülü tarafından amaç koda dönüştürülür.

kaynak kod -> önişlemci -> derleyici -> amaç program

C programlama dilinde # ile başlayan bütün satırlar önişlemciye verilen komutlardır.
# karakterinin sağında bulunan sözcükler ki bunlara önişlemci komutları denir (preprocessor
directives), önişlemciye ne yapması gerektiğini anlatır. Örneğin:
#include
#define
#if
#ifdef
#ifndef
hepsi birer önişlemci komutudur.
Önişlemci komutları derleyici açısından anahtar sözcük değildir # karakteriyle birlikte anlam
kazanırlar, yani istersek include isimli bir değişken tanımlayabiliriz ama bunun okunabilirlik
açısından iyi bir fikir olmadığını söyleyebiliriz. Önişlemci komutlarını belirten sözcükler ancak
sol taraflarındaki # karakteriyle kullanıldıkları zaman özel anlam kazanırlar ki bunlara önişlemci
anahtar sözcükleri de diyebiliriz.
Önişlemci amaç kod oluşturmaya yönelik hiçbir iş yapmaz. Önişlemci kaynak programı # içeren
satırlardan arındırır. Derleme modülüne girecek programda artık # içeren satırlar yer
almayacaktır.
Bir çok önişlemci komutu olmakla birlikte şimdilik yalnızca #include komutu ve #define
önişlemci komutlarını inceleyeceğiz. Geriye kalan önişlemci komutlarını da ileride detaylı olarak
inceleyeceğiz.
#include önişlemci komutu
genel kullanım biçimi:
#include <dosya ismi>
ya da
#include "dosya ismi"
#include, ilgili kaynak dosyanın derleme işlemine dahil edileceğini anlatan bir önişlemci
komutudur. Bu komut ile önişlemci belirtilen dosyayı diskten okuyarak komutun yazılı olduğu
yere yerleştirir. (metin editörlerindeki copy – paste işlemi gibi)
#include komutundaki dosya ismi iki biçimde belirtilebilir:
1. Açısal parantezlerle:
#include <stdio.h>
#include <time.h>
2. Çift tırnak içerisinde:
#include "stdio.h"
#include "deneme.c"
Dosya ismi eğer açısal parantezler içinde verilmişse, sözkonusu dosya önişlemci tarafından
yalnızca önceden belirlenmiş bir dizin içerisinde aranır. Çalıştığımız derleyiciye ve sistemin
kurulumuna bağlı olarak, önceden belirlenmiş bu dizin farklı olabilir. Örneğin:
\TC\INCLUDE
\BORLAND\INCLUDE
\C600\INCLUDE
gibi. Benzer biçimde UNIX sistemleri için bu dizin, örneğin:
/USR/INCLUDE
biçiminde olabilir. Genellikle standart başlık dosyaları önişlemci tarafından belirlenen dizinde
olduğundan, açısal parantezler ile kaynak koda dahil edilirler.
Dosya ismi iki tırnak içine yazıldığında önişlemci ilgili dosyayı önce çalışılan dizinde (current
directory), burada bulamazsa bu kez de sistem ile belirlenen dizinde arayacaktır. Örneğin:
C:\SAMPLE
dizininde çalışıyor olalım.
#include "strfunc.h" komutu ile önilemci strfunc.h dosyasını önce C:\SAMPLE dizininde arar.
Eğer burada bulamazsa bu kez sistem ile belirlenen dizinde arar. Programcıların kendilerinin
oluşturdukları başlık dosyaları genellikle sisteme ait dizinde olmadıkları için iki tırnak içinde
kaynak koda dahil edilirler.
#include ile koda dahil edilmek istenen dosya ismi dosya yolu da (path) de içerebilir:
#include <sys\stat.h>
#include "c:\headers\myheader.h"
....
gibi.
Bu durumda açısal parantez ya da iki tırnak gösterimleri arasında bir fark yoktur. Her iki
gösterimde de önişlemci ilgili dosyayı yalnızca yolun belirttiği dizinde arar.
#include komutu ile yalnızca baslık dosyalarının koda dahil edilmesi gibi bir zorunluluk yoktur.
Herhangi bir kaynak dosya da bu yolla kaynak koda dahil edilebilir.

#include "topla.c"
int main()
{
toplam = a + b;
printf("%d", toplam);
return 0;
}
topla.c
int a = 10;
int b = 20;
int toplam;
Yukarıdaki örnekte #include "topla.c" komutu ile önişlemci komutunun bulunduğu yere topla.c
dosyasının içeriğini yerleştirecek böylece prog.c dosyası derlendiğinde herhangi bir hata
oluşmayacaktır.
Çünkü artık derleme işlemine girecek dosya:
int a = 10;
int b = 20;
int toplam;
int main()
{
toplam = a + b;
printf("%d", toplam);
return 0;
}
şeklinde olacaktır.
#include komutu kaynak programın herhangi bir yerinde bulunabilir. Fakat standart başlık
dosyaları gibi, içerisinde çeşitli bildirimlerin bulunduğu dosyalar için en iyi yer kuşkusuz
programın en tepesidir.
#include komutu içiçe geçmiş (nested) bir biçimde de bulunabilir.

#define Önişlemci Komutu
#define önişlemci komutu text editörlerindeki bul ve değiştir özelliği (find & replace) gibi
çalışır. Kaynak kod içerisindeki bir yazıyı başka bir yazı ile değiştirmek için kullanılır.
Önişlemci define anahtar sözcüğünden sonra boşlukları atarak ilk boşluksuz yazı kümesini elde
eder. (Buna STR1 diyelim.) Daha sonra boşlukları atarak satır sonuna kadar olan tüm
boşlukların kümesini elde eder. (Buna da STR2 diyelim) Kaynak kod içerisinde STR1 yazısı
yerine STR2 yazısını yerleştirir. (STR2 yazısı yalnızca boşluktan ibaret de olabilir. Bu durumda
STR1 yazısı yerine boşluk yerleştirilir, yani STR1 yazısı silinmiş olur.) Örnekler :
#define SIZE 100
önişlemci komutuyla, önişlemci kaynak kod içerisinde gördüğü tüm SIZE sözcükleri yerine 100
yerleştirecektir. Derleme modülüne girecek kaynak programda artık SIZE sözcüğü hiç yer
almayacaktır.
#define kullanılarak bir yazının bir sayısal değerle yer değiştirmesinde kullanılan yazıya
"sembolik sabit" (symbolic constants) denir. Sembolik sabitler nesne değildir. Derleme
modülüne giren kaynak kodda sembolik sabitler yerine bunların yerini sayısal ifadeler almış
olur.
Sembolik sabitler basit makrolar (simple makro) olarak da isimlendirirler ama biz "sembolik
sabit" demeyi yeğliyoruz.
Sembolik sabitlerin kullanılmasında dikkatli olunmalıdır. Önişlemci modülünün herhangi bir
şekilde aritmetik işlem yapmadığı yalnızca metinsel bir yer değiştirme yaptığı unutulmamalıdır:
#define MAX 10 + 20
int main()
{
int result;
result = MAX * 2;
printf("%d\n", result);
return 0;
}
Yukarıdaki örnekte result değişkenine 50 değeri atanır. Ancak ön işlemci komutunu
#define MAX (10 + 20)
şeklinde yazsaydık, result değişkenine 60 değeri atanmış olurdu.
Bir sembolik sabit başka bir sembolik sabit tanımlamasında kullanılabilir. Örneğin:
#define MAX 100
#define MIN (MAX - 50)
Önişlemci " " içerisindeki yazılarda değişiklik yapmaz. (stringlerin bölünemeyen atomlar
olduğunu hatırlayalım.) Yer değiştirme işlemi büyük küçük harf duyarlığı ile yapılır. Ancak
sembolik sabitler geleneksel olarak büyük harf ile isimlendirilirler. Bunun nedeni, kodu okuyan
kişinin değişkenlerle sembolik sabitleri ayırt edebilmesine yardımcı olmaktır. Bilindiği gibi
geleneksel olarak değişken isimlendirmesinde C dilinde ağırlıklı olarak küçük harfler
kullanılmaktadır.
#define komutu ile ancak değişkenler ve anahtar sözcükler yer değiştirilebilir. Sabitler ve
operatörler yer değiştiremez.
Aşağıdaki #define önişlemci komutları geçerli değildir:
#define + -
#define 100 200

Sembolik sabitler C dilinin değişken isimlendirme kurallarına uygun olarak isimlendirilir:
#define BÜYÜK 10
tanımlamasında hata oluşur. (Hata tanımlama satırında değil sembolik sabitin kod içinde
kullanıldığı yerlerde oluşur. Tanımlanan sembolik sabit kod içinde kullanılmazsa hata da
oluşmayacaktır.)
Önişlemci #include komudu ile kaynak koda dahil edilen dosyanın içerisindeki önişlemci
komutlarını da çalıştırır. İçinde semboli sabit tanımlamaları yapılmış bir dosya #include komudu
ile kaynak koda dahil edildiğinde, bu sembolik sabitler de kaynak kod içinde tanımlanmış gibi
geçerli olur.
#define sembolik sabit tanımlamalarında stringler de kullanılabilir :
#define HATA_MESAJI "DOSYA AÇILAMIYOR \n"
...
printf(HATA_MESAJI);
...
Sembolik sabit tanımlamasında kullanılacak string uzunsa okunabilirlik açısından birden fazla
satıra yerleştirilebilir. Bu durumda son satır dışında diğer satırların sonuna "\" karakteri
yerleştirilmelidir.
Önişlemci değiştirme işlemini yaptıktan sonra #define komutlarını koddan çıkartır.
Okunabilirlik açısından tüm sembolik sabit tanımlamaları alt alta getirecek şekilde yazılmalıdır.
Seçilen sembolik sabit isimleri kodu okuyan kişiye bunların ne amaçla kullanıldığı hakkında fikir
vermelidir.
C'nin başlık dosyalarında bazı sembolik sabitler tanımlanmıştır. Örneğin stdio.h içerisinde
#define NULL 0
biçiminde bir satır vardır. Yani stdio.h dosyası koda dahil edilirse NULL sembolik sabiti 0 yerine
kullanılabilir. math.h içinde de pek çok matematiksel sabit tanımlanmıştır.
Bir sembolik sabitin tanımlanmış olması kaynak kod içerisinde değiştirilebilecek bir bilginin
olmasını zorunlu hale getirmez. Tanımlanmış bir sembolik sabitin kaynak kod içerisinde
kullanılmaması hataya yol açmaz.
#define Önişlemci Komutu Neden Kullanılır
1. Okunabilirliği ve algınalabilirliği artırır. Bir takım sabitlere onların ne amaçla kullanıldığını
anlatan yazılar karşılık getirilirse programa bakan kişiler daha iyi anlamlandırır.
#define PERSONEL_SAYISI 750
int main()
{
if (x == PERSONEL_SAYISI)
...
return 0;
}
2. Bir sabitin program içerisinde pekçok yerde kullanıldığı durumlarda değiştirme işlemi tek
yerden yapılabilir. Böylece söz konusu program sembolik sabite baglı olarak yazılıp, daha
sonra sembolik sabitin değiştirilmesiyle farklı parametrik değerler için çalıştırılabilir.

3. Sayısal sabitlerin kullanılmasında tutarsızlıkları ve yazım yanlışlarını engeller.
Örneğin matematiksel hesaplamalar yapan bir kodda sıksık pi sayısını kullandığımızı
düşünelim. pi sayısı yerine
#define PI 3.14159
sembolik sabitini kullanabiliriz. Her defasında pi sayısının sabit olarak koda girersek, her
defasında aynı değeri yazamayabiliriz. Örneğin bazen 3.14159 bazen 3.14156
girebileceğimiz gibi yanlışlıkla 3.15159 da girebiliriz. Sembolik sabit kullanımı bu tür
hataları ortadan kaldırır.
4. Sembolik sabitler kullanılarak C sintaksında küçük değişiklikler yapılabilir.
#define FOREVER for(;;)
#define BEGIN {
#define END }
Böyle bir sembolik sabit kullanımını kesinlikle tavsiye etmiyoruz. Okunabilirliği artırması
değil azaltması söz konusudur. Bir C programcısı için en okunabilir kod C'nin kendi
sentaksının kullanıldığı koddur.
#define komutu kaynak kodun herhangi bir yerinde kullanılabilir. Ancak tanımlandığı yerden
kaynak kodun sonuna kadar olan bölge içerisinde etki gösterir.
Ancak en iyi tanımlama yeri kaynak kodun tepesidir. Geleneksel olarak sembolik sabitler
#include satırlarından (bir satır boşluk verildikten sonra) hemen sonra yer alırlar.
Sembolik Sabitlerin Kullanılmasında Çok Yapılan Hatalar
1. Sembolik sabit tanımlamasında gereksiz yere = karakterini kullanmak.
#define N = 100 /* yanlış */
Bu durumda önişlemci N gördüğü yere = 100 yapıştıracaktır. Örneğin int a[N] gibi bir dizi
tanımlanmışsa önişlemci bu tanımlamayı a[N = 100] yapar ki bu da derleme aşamasında
hata (error) ile neticelenir.
2. Sembolik sabit tanımlama satırını ; ile sonlandırmak.
#define N 100; /* yanlış */
Bu durumda önişlemci N gördüğü yere 100; yerleştirir. int a[N]; tanımlaması int a[100;]
haline gelir. Derleme aşamasında hata oluşur. (Borland derleyicileri hata mesajı :Array
bounds missing ])Bu tür hatalarda derleyici sembolik sabit kaç yerde kullanılmışsa o kadar
error verecektir.
3. Sembolik sabitlerle yer değiştirecek ifadelerin operatör içermesi durumunda bu ifadeleri
paranteze almadan yerleştirmek.
#define SIZE 5 + 10
...
i = SIZE * 5 /* 5 + 10 * 5 = 55 (75 değil) */

Hiç yorum yok:

Yorum Gönder

Her yorum bilgidir. Araştırmaya devam...