original in en Lorne Bailey
en to tr Bülent ÖZDEMİR
Lorne Şikago da yaşıyor ve Oracle veritabanlarından veri alma-girme konusunda uzmanlaşmış bir bilgisayar danışmanıdır. Unix ortamında programlama yapmaya başladığından beri, Lorne 'DLL Cehennemi' nden tamamıyla kaçınmıştır. Halen Bilgisayar Bilimleri'nde yüksek lisansı için çalışmaktadır.
Serbest bir yazılımın kapalı ve tescilli bir derleyici ile derlendiğini
düşünebilir misiniz? Programınızın çalışan kısmına neler olacağını nereden
bilebilirsiniz? Herhangi bir 'arka kapı' veya Truva Atı virüsü olabilir.
Ken Thompson, tüm zamanların en büyük hack lerinden birinde, 'login'
programının içine bir arka kapı koyan ve derleyici kendisini
derlemeye başladığını anladığı zaman kendisini koruyan bir
derleyici yazmıştır. Bu tüm zamanların klasiğini, kendi tanımlamasından,
bu adresten okuyabilirsiniz.
Şanslıyız ki, gcc var. Bir configure; make; make install
yaptığınızda,
gcc sahne arkasında birçok ağır işi sizin için yapmaktadır.
gcc'nin bizim için çalışmasını nasıl sağlarız?
Bir kart oyunu yazmaya başlayacağız, ancak derleyicinin fonksiyonlarını
gosterecek kadarıyla yetineceğiz. Sıfırdan başlayacağımdan, bir
çalıştırılabilir program yaratılabilmesi için
derleyicinin neler yapması gerektiğini ve bunları hangi sırada
yaptığını anlamamız gerekiyor. Bir C programının nasıl derlendiğine
ve gcc'nin bizim istediklerimizi yapması için gereken seçeneklerine
bakacağız.
Adımlar (ve bu adımları gerçekleştiren araçlar) şunlardır:
Ön-derleme (gcc -E),
Derleme (gcc),
Toplama (as),
ve Bağlama (ld).
İlk önce, derleyici nasıl çalıştıracağımızı bilmeliyiz. Tüm zamanların klasiği olan ilk C programı ile başlayacağız. (Deneyimliler beni affetmek zorunda).
#include <stdio.h> int main()
{ printf("Hello World!\n"); }
Bu dosyayı game.c
olarak kaydedın. Programı, komut satırında:
yazarak derleyebilirsiniz.
C derleyicisi, benimsenmiş olarak, a.out
isimli çalıştırılabilir program oluşturur.
Bunu, şöyle çalıştırabilirsiniz:
a.out Hello WorldHer derleme yaptığınızda yeni
a.out
eskisinin üzerine
yazılacaktır. Bu yüzden, a.out
dosyasını hangi programın
oluşturduğunu bilemezsiniz. Bu problemı, gcc'ye çalıştırılabilir ismini
-o
seçeneği ile söyleyerek çözebiliriz. Bu programı
game
olarak adlandıracağız, ama, C, Java gibi isimlendirme
sınırlaması gerektirmediğinden herhangi bir isim de verebiliriz.
gcc -o game game.c
game Hello World
Bu noktada, çok faydalı bir programa sahip olmaktan hayli uzağız. Bunun kötü birşey olduğunu düşünüyorsanız, derlenmiş ve çalışan bir programımız olduğu gerçeğini anımsayabilirsiniz. Bu programa küçük küçük eklemeler yaptıkça, programın hala çalışır olduğundan emin olmak isteyeceğiz. Öyle görünüyor ki, her yeni başlayan programcı 1,000 satırlık kaynak kod yazıp tek seferde düzeltmeleri yapmak ister. Kimse, evet kimse, bunu yapamaz. Çalışan kücük bir program yazarsınız, değişiklikleri yaparsınız ve tekrar çalışabilir hale getirirsiniz. Bu, tek seferde düzeltmeniz gereken hata sayısını sınırlar. Artı, son olarak ne yaptığınızı bildiğinizden, çalışmama durumunda nereye odaklanacağınızı bilirsiniz. Bu çalışacağını sizin düşündüğünüz, derlenen, ama çalışmayan birşey yapmaktan sizi alıkoyar. Hatırlayın, derlenebilir olması dogru olduğu anlamına gelmez.
Bir sonraki adımımız oyunumuz için bir başlık dosyası oluşturmak. Bir başlık dosyası tek bir yerde, veri tipleri ve fonksiyon tanımlamaları için kullanılır. Bu veri yapılarının tutarlı bir şekilde tanımlanmasını sağlar ki, böylece programın her kısmı herşeyi aynı şekilde tanır.
#ifndef DECK_H #define DECK_H #define DECKSIZE 52 typedef struct deck_t { int card[DECKSIZE]; /* number of cards used */ int dealt; }deck_t; #endif /* DECK_H */
Bu dosyayı deck.h
olarak kaydedin. Sadece .c
dosyaları
derlenebilir, bu yüzden game.c programımızı değiştireceğiz.
game.c dosyasının ikinci satırına, #include "deck.h"
yazın.
Beşinci satıra, deck_t deck;
yazın. Herhangi bir
yanlış yapmadığımızdan emin olmak için yeniden derleyin.
gcc -o game game.c
Hata yok, problem yok. Derlenmezse, derlenene kadar uzerinde çalışın.
Derleyici deck_t
tipini nereden biliyor?
Çünkü ön-derleme sırasında, "deck.h" dosyası "game.c"
dosyasına kopyalanır. Kaynak kodun içerisinde önderleyici komutlarının
önüne # konulur. gcc'yi -E
anahtarı ile kullanarak
ön-derleyiciyi çağırabilirsiniz.
gcc -E -o game_precompile.txt game.c wc -l game_precompile.txt 3199 game_precompile.txtHemen hemen 3200 satır çıktı! Büyük çoğunluğu
stdio.h
include dosyası, fakat baktığınızda, bizim tanımlamalarımız da
orada. Eğer -o
anahtarı ile bir çıktı dosyası belirtmezseniz
, çıktılar konsola yazılır. Ön-derleme işlemi uç temel amacı gerçekleştirerek
kaynak koda büyük esneklik verir:
-E
anahtarını hemen hemen hiç kullanmazsınız,
ama bırakın çıktısını derleyiciye aktarsın.
Bir ara adım olarak, gcc kodunuzu Assembly diline çevirir. Bunu yapmak için, kodunuzu parse ederek ne yapmaya çalıştığınuzu ortaya çıkarması gerekir. Eğer bir yazım(syntax) hatası yapmış iseniz, bunu size söyler ve derleme durur. İnsanlar, hatalı olarak, bu adımı bütün işlemin kendisi gibi algılarlar. Ancak, gcc'nin daha yapacağı çok iş vardır.
as
Assembly kodunu nesne koduna çevirir. Nesne kodu
aslında CPU tarafından çalıştırılamaz, ama çalıştışılmaya oldukça yakındır.
-c
derleyici anahtarı .c dosyasını .o uzantılı bir nesne
dosyasına donuşturur. Eğer
gcc -c game.ckomutunu calıştırırsak otamatik olarak game.o isimli bir dosya elde ederiz. Burada önemli bir noktada duralım. Herhangi bir .c dosyasını alarak bir nesne dosyası oluşturabiliriz. Aşağıda göreceğimiz gibi, bu nesne dosyalarını Bağlama adımı ile çalışabilir bir dosyaya toplayabiliriz. Örneğimizle devam edelim. Bir kart oyunu programlıyoruz ve kağıt destesini
deck_t
ile tanımladık, şimdi kağıtları karıştıracak bir fonksiyon yazacağız.
Bu fonksiyon, deste tipine bir işaretçi alacak ve onu, rastgele bir sayı kümesi
ile dolduracak. Hangi kartların daha önce kullanildiğini 'drawn' dizisi ile
takip edecek. Bu dizi DECKSIZE üyelerinden birini iki kez kullanmamızı engelliyecek.
#include <stdlib.h> #include <stdio.h> #include <time.h> #include "deck.h" static time_t seed = 0; void shuffle(deck_t *pdeck) { /* Keeps track of what numbers have been used */ int drawn[DECKSIZE] = {0}; int i; /* One time initialization of rand */ if(0 == seed) { seed = time(NULL); srand(seed); } for(i = 0; i < DECKSIZE; i++) { int value = -1; do { value = rand() % DECKSIZE; } while(drawn[value] != 0); /* mark value as used */ drawn[value] = 1; /* debug statement */ printf("%i\n", value); pdeck->card[i] = value; } pdeck->dealt = 0; return; }
Bu dosyayı shuffle.c
olarak kaydedin.
Kodun içerisine bir hata ayıklama satırı koyduk, öyle ki,
program çalıştırıldığı zaman, bu satır üretilen kart
numaralarını yazacak. Bu programın fonksiyona litesine
herhangi bir katkı da bulunmayacak, ama neler olduğunu
görmek açısından önemli. Henüz oyunumuza yeni başladığımızdan
fonksiyonumuzun çalışıp çalışmadığını anlamak için başka
yolumuz yok. printf satırı sayesinde şu anda tam olark ne olduğunu
anlayabiliriz, böylece destemizin doğru dağıtıldığından emin olarak
bir sonraki adıma geçebiliriz. Çalıştığından iyice emin olduktan sonra,
hata ayıklama satırını programdan çıkartabiliriz. Bu hata ayıklama tekniği çok ham görünebilir,
ama çok küçük bir hareketle amacımıza ulaşıyoruz. Daha gelişmiş
hata ayıklayıcıları daha sonra tartışacağız.
shuffle.c
dosyasında 'main' fonsiyonu olmadığından
bu programı çalıştıramayız. Bu dosyayı, içerisinde 'main' fonksiyonu
olan ve 'shuffle' fonksiyonu çağırımı yapan başka bir program ile birleştirmeliyiz.
gcc -c shuffle.ckomutunu çalıştırın ve
shuffle.o
dosyası oluştuğundan
emin olun. game.c dosyasını açın, ve 7. satırda, deck
değişkeni
için yapılan deck_t tanımlamasından sonra,
shuffle(&deck);satırını ekleyin. Şimdi, daha önce yaptığımız gibi çalışan program oluşturmaya çalıştığımız zaman, hata mesajı alacağız:
gcc -o game game.c /tmp/ccmiHnJX.o: In function `main': /tmp/ccmiHnJX.o(.text+0xf): undefined reference to `shuffle' collect2: ld returned 1 exit statusDerleme başarılı oldu, cunku yazim hatamız yoktu. Bağlama adımı başarısız oldu, çünkü derleyiciye 'shuffle' fonksiyonunun nerede olduğunu soylemedik. Bağlama nedir ve derleyiciye bu fonksiyonu nerede bulacağını nasıl söyleyeceğiz?
Bağlayıcı,ld
, daha once as
ile oluşturulmuş
nesne kodunu alır ve onu
gcc -o game game.o shuffle.okomutuyla çalışabilir hale getirir. Bu iki nesneyi biraraya getirir ve
game
isimli
calışabilir dosyayı oluşturur.
Bağlayıcı shuffle
fonksiyonunu shuffle.o nesnesinden
bulur ve çalışır dosyaya ekler. Nesne dosyalarının güzel tarafı,
fonksiyonu tekrar kullanmak istediğimizde, tüm yapmamiz gereken
"deck.h" dosyasını eklemek ve shuffle.o
nesne dosyasını
yeni çalışır dosyanın içine koymaktan ibarettir.
Bunun gibi kodun yeniden kullanımı herzaman karşılaşılan bir durumdur.
Hata ayıklama satırı olarak kullandığımız printf
fonksiyonunu
kendimizin yazmamamyzyn sebebi, bağlayıcı bu fonksiyonun tanımlamasını
#include
<stdlib.h>
dosyasında bulur ve (/lib/libc.so.6) C kütüphanesinde
bulunan nesne koduna bağlar.
Bu yolla, başkasının yazdığı çalışan bir fonksiyonu kullanıp kendi problemlerimizi
çözmekle uğraşabiliriz. Başlık dosyalarının sadece veri ve fonksiyon tanımlamalarını
içermesinin sebebi de budur. Normalde, nesne dosyalarını veya kütüphanelerini
bağlayıcının çalışır dosyaya koyması için yaratırız.
Kodumuzla ilgili bir problem oluşabilir, çünkü başlık dosyamıza herhangi
bir fonksiyon tanımlaması koymadik.
-Wall
anahtari, butun dil yazım ikazlarını,
kodumuzun doğru ve mümkün olduğu kadar taşınabilir olduğundan emin
olmamiz için etkin hale getirir.
Bu anahtarı kullanarak kodumuzu derlediğimizde, şunun gibi birşey görürüz:
game.c:9: warning: implicit declaration of function `shuffle'Bu, biraz daha işimizin olduğunu bize bildirir.
shuffle
fonksiyonunun nerede olduğunu derleyiciye bildirmek için header dosyasına
bir satır eklememiz gerekiyor, böylece derleyici ihtiyaç duyduğu bütün
denetimleri yapabilir.
Biraz garip gözüküyor, ama bu bize tanımlamaları gerçekleştirim kısmından ayırmamızı
ve fonksiyonumuzu, sadece yeni başlık dosyasına koyup nesne dosyasını birleştirerek
istediğimiz yerde kullanmamızı sağlıyor.
Şu tek satırı deck.h dosyasına koyacağız.
void shuffle(deck_t *pdeck);Bu bütün ikaz mesajlarını etkisiz hale getirecek.
Yaygın olarak kullanılan ikinci bir derleyici seçeneği optimizasyondur.
-O#
(i.e. -O2).
Bu derleyiciye hangi seviyede optimizasyon yapacağımızı sçyler.
Derleyici, kodun daha hızlı çalışabilmesi için bir çok hünere sahiptir.
Bizimki gibi minik bir program için farkı anlamayabiliriz, ama büyük programların
farkedilir şekilde hızlı çalışmasını sağlayabilir. Buna heryerde rastlayabilisiniz,
o yüzden ne anlama geldiğini bilmeniz gerekiyor.
Hepimizin bildiği gibi, programımızın derlenebiliyor olamsı, onun istediğimiz şekilde çalişacaği anlamına gelmez. Bütün numaraların bir kez kullanıldığından emin olmak için:
game | sort - n | lesskomutunu çalıştırıp, eksik birşey olmadığını denetleriz. Bir problem varsa ne yapacağız? Nasıl örtünün altına bakıp, hatalarımızı bulacağız? Kodunuzu bir hata ayıklayıcı ile kontrol edebilirsiniz. Birçok sürüm klasik hata ayıklayıcı gdb'yi sağlar. Komut satırı seçenekleri benim gibi sizi de mutsuz ediyorsa, KDE'nin KDbg adresinde sunduğu güzel bir önyüzü kullanabilirsiniz. Birbirine çok benzeyen başka önyüzler de vardir. Hata ayıklamaya başlamak için File->Executable seçin ve
game
programını bulun.
F5 e bastiginizda ya da menuden Execution->Run seçtiğinizde, çıktıyı
ayrı bir pencerede görmelisiniz. Ne oldu? Pencerede hiçbirşey gçremediniz mi?
Telaşlanmayın, KDbg'ye birşey olmadı. Problemin kaynağı, bizim çalışabilir
dosyaya herhangi bir hata ayıklama bilgisi koymamış olmamız, bu yüzden KDbg
içeride neler olduğunu söyleyemez. -g
anahtarı gerekli bilgiyi
nesne dosyalarına koyar. Nesne dosyalarınızı (.o uzantılı) bu anahtar ile
derlemelisiniz, şimdi komut:
gcc -g -c shuffle.c game.c gcc -g -o game game.o shuffle.ooldu. Bu, çalışabilir dosyanıza gdb ve KDbg neler olduğunu anlamasını sağlayan çengeller koyar. Hata ayıklama önemli bir yetenektir, bir tanesini iyi bilmeye zaman ayırmanıza değer. Hata ayıklayıcıların programcılara yardım edebilmesini sağlayan şey kaynak kodda 'Breakpoint' koyabilmesidir. Şimdi,
shuffle
fonksiyonunu çağıran satıra bir tane eklemeyi deneyin. Küçük bir kırmızı
halkanın satırın yanında belirmesi lazım. Şimdi, F5'e bastığınızda program
o satırda duracaktır. F8 basarak shuffle fonksiyonunun içerisine
bir adım atın. Hey, şimdi shuffle.c
dosyasının koduna
bakıyoruz! Programın çalışmasını adım adım takip ederek neler olduğunu
görebiliriz. Farenin işaretçisini bir yerel değişkenin üstüne getirdiğinizde
hangi değere sahip olduğunu görebilirsiniz. Şirin. printf
satırlarından
daha iyi değil mi?
Bu makale, sizlere, C programlarının derlenmesi ve hatalarının ayıklanması
konusunda kısa bir tur sundu. Derleyicinin geçtiği adımları ve
bu adımların gerçekleşmesi için gcc'ye verilen anahtarları inceledik.
Paylaşılan kütüphanelerin bağlanmasına bakıp, hata ayıklayıcılara bir
girişle bitirdik. Ne yaptığınızı oğrenmek için daha çok çalışmaya
gereksinim
var, ama ümit ederim bu makale doğru adımla başlanmasına hizmet eder.
Daha geniş bilgiyi, gcc
, as
ve ld
nin man
ve info
sayfalarından bulabilirsiniz.
En çok şeyi, kod yazarken oğrenirsiniz. Pratik için, bu makaledeki kart programını kullanarak bir blackjack oyunu yazabilirsiniz. Hata ayıklayıcı kullanımı oğrenmek için biraz zaman ayırın. KDbg gibi bir GUI ile başlamak daha kolaydır. Her seferinde biraz fonksiyonalite eklerseniz, oğrenme işini halletmiş olursunuz. Hatırlatma, programınızı daima çalışır halde tutun.
Tam bir oyun yazmak için gereksiniminiz olan şeylerden bazılarının listesi.