

|Bu makalenin farklı dillerde bulunduğu adresler: English Castellano ChineseGB Deutsch Francais Italiano Portugues Russian Turkce Arabic|


tarafından Lorne Bailey
<sherm_pbody(at)yahoo.com>
Yazar hakkında:
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.
Türkçe'ye çeviri:
Bülent ÖZDEMİR <bulentozdem(at)hotmail.com>
İçerik:
Özet:
Bu yazı, sizin C dilinin temellerini bildiğinizi varsayarak
derleyici olarak gcc kullanımını size tanıtacaktır. Basit C kaynak
kodlarınız için derleyiciyi komut satırından çağırabileceksiniz.
Daha sonra, aslında neler olduğuna ve programlarınızın
derlenmesini nasıl denetleyebileceğinize hızlıca bir göz atacağız.
Bu arada hata ayıklayıcıların kullanımına da çok kısa değineceğiz.
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.outeskisinin üzerine yazılacaktır. Bu yüzden,
a.outdosyasını hangi programın oluşturduğunu bilemezsiniz. Bu problemı, gcc'ye çalıştırılabilir ismini
-oseçeneği ile söyleyerek çözebiliriz. Bu programı
gameolarak 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.hinclude dosyası, fakat baktığınızda, bizim tanımlamalarımız da orada. Eğer
-oanahtarı 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:
-Eanahtarı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_tile 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.cdosyası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.odosyası oluştuğundan emin olun. game.c dosyasını açın, ve 7. satırda,
deckdeğ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
gameisimli 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.
shufflefonksiyonunun 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
gameprogramı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.
-ganahtarı 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,
shufflefonksiyonunu ç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.cdosyası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.
printfsatı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.


Görselyöre sayfalarının bakımı, LinuxFocus Editörleri tarafından yapılmaktadır


© Lorne Bailey, FDL
LinuxFocus.org
Burayı klikleyerek hataları rapor edebilir ya da yorumlarınızı LinuxFocus'a gönderebilirsiniz


Çeviri bilgisi:


2002-03-04, generated by lfparser version 2.27