29 Temmuz 2020 Çarşamba

C programlama stringler

STRINGLER
C programa dilinde iki tırnak içerisindeki ifadelere string ifadeleri ya da kısaca stringler denir.
Örneğin:
"sifir zero"
"x = %d\n"
"lütfen bir sayı giriniz : "
ifadelerinin hepsi birer stringdir.
Stringlerin tek bir atom olarak ele alındığını önceki derslerden hatırlıyoruz. C'de stringler
aslında char türden bir adres olarak ele alınmaktadır. C derleyicileri, derleme aşamasında bir
stringle karşılaştığında, önce bu stringi belleğin güvenli bir bölgesine yerleştirir, sonuna NULL
karakteri ekler ve daha sonra string yerine yerleştirildiği yerin başlangıç adresini koyar. Bu
durumda string ifadeleri aslında stringlerin bellekteki başlangıç yerini gösteren char türden
birer adrestir. Örneğin:
char *p;
...
p = "sifir zero";
gibi bir kodun derlenmesi sırasında, derleyici önce "sifir zero" stringini belleğin güvenli bir
bölgesine yerleştirir; daha sonra yerleştirdiği yerin başlangıç adresini string ifadesi ile değiştirir.
Stringler char türden bir adres olarak ele alındığına göre char türden göstericilere
atanmalarında error ya da uyarı gerektiren bir durum söz konusu değildir.
Stringlerin Fonksiyonlara Arguman Olarak Gönderilmesi
Parametre değişkeni char türden bir gösterici olan fonksiyonu char türden bir adres ile
çağırmak gerektiğini biliyoruz. Çünkü doğal olarak char türden bir göstericiye char türden bir
adres atanmalıdır.
Derleyiciler açısından stringler de char türden bir adres belirttiklerine göre, parametre
değişkeni char türden gösterici olan bir fonksiyonu bir string ile çağırmak son derece doğal bir
durumdur, ve C dilinde bu yapı çok kullanılır:
puts("sifir zero");
Burada derleyici "sifir zero" stringini belleğe yerleştirip sonuna NULL karakteri koyduktan
sonra artık bu stringi, karakterlerini yerleştirdiği bellek bloğunun başlangıç adresi olarak
görecektir. puts fonksiyonunun parametre değişkenine de artık char türden bir adres
kopyalanacaktır. puts fonksiyonu parametre değişkeninde tutulan adresten başlayarak NULL
karakteri görene kadar tüm karakterleri ekrana yazmaktadır. Bu durumda ekranda
Merhaba
yazısı çıkacaktır. Başka bir örnek:
char str[20];
...
strcpy(str, "sifir zero");
Bu örnekte de "sifir zero" stringi str adresinden başlayarak kopyalanmaktadır. String
ifadelerinin bulunduğu yerde char türden bir adresin bulunduğu düşünülmelidir. Şimdi
aşağıdaki ifadeyi yorumlayalım:
strcpy("sifir", "zero");

Derleyici derleme aşamasında iki stringi de bellekte güvenli bir bölgeye yerleştirir. Çalışma
zamanı sırasında strcpy fonksiyonu "sifir" stringini gösteren adresten başlayarak "zero" stringini
gösteren adrese NULL karakter görene kadar kopyalama yapacağına göre, bu ifadenin faydalı
hiçbir anlamı olmayacaktır. Üstelik bu ifade, ikinci string birinciden uzun olduğu için bir
gösterici hatasına da neden olur. Şimdi aşağıdaki ifadeyi inceleyelim:
p = "zero" + "sifir";
Yukarıdaki örnekte aslında "sifir" stringinin başlangıç adresi ile "zero" stringinin başlangıç
adresi toplanmaktadır. Peki bu toplamın yararlı bir sonucu olabilir mi? Derleyiciler
anlamsızlığından dolayı iki adresin toplanmasına izin vermezler. Fakat bir göstericiden başka bir
göstericinin değerini çıkarmanın yararlı gerekçeleri olabilir. Bu yüzden göstericilerin birbirinden
çıkartılması derleyicilerin hepsinde geçerli bir işlemdir. Gösterici aritmetiğine göre bir adresten
aynı türden bir başka bir adres çıkartıldığı zaman elde edilen sonuç bir tamsayıdır, ve bu sayı
iki adresin sayısal bileşenlerinin farkı değerinin adresin tür uzunluğuna bölünmesiyle elde edilir:

#include <stdio.h>
int main()
{
char *p = (char *) 0x1AA8;
char *q = (char *) 0x1AA0;
long *lp = (long *) 0x1AA8;
long *lq = (long *) 0x1AA0;
printf("fark1 = %d\n", p - q);
printf("fark2 = %d\n", lp - lq);
return 0;
}
Yukarıdaki programın çalıştırılmasıyla ekrana:
fark1 = 8
fark2 = 2
yazılacaktır.

C derleyicileri kaynak kodun çeşitli yerlerinde tamamen özdeş stringlere rastlasa bile bunlar
için farklı yerler ayırabilirler. Derleyici özdeş stringlerin sadece bir kopyasını da bellekte
saklayabilir. Özdeş stringlerin nasıl saklanacağı bazı derleyicilerde programcının seçimine
bırakılmıştır. Derleyicinin ayarları arasında bu konuyla ilgili bir seçenek bulunur.
Örneğin bir programın içerisinde:
printf("Dosya açılamıyor!..\n");
...
printf("Dosya açılamıyor!..\n");
...
printf("Dosya açılamıyor!..\n");
ifadelerinin bulunduğunu varsayalım. Farklı yerlerde "Dosya açılamıyor!..\n" gibi özdeş stringler
bulunsa bile derleyici bunlar için tekrar ve farklı yerler ayırabilir. Bu da büyük uygulamalar için
belleğin verimsiz kullanılmasına yol açabilir.
Bellekte yer ayırma işlemi derleme aşamasında yapılmaktadır. Aşağıdaki kod parçasının
çalıştırılmasıyla ekrana hep aynı adres basılacaktır :
char *p;
int k;
...
for (k = 0; k < 10; ++k) {
p = "sifir zero";
printf("%p\n", p);
}
...
Eğer yer ayırma işlemi çalışma zamanı sırasında yapılsaydı, o zaman farklı adresler basılabilirdi.
Stringlerin doğrudan karşılaştırılması yanlış bir işlemdir.
...
if (“izmir” == “izmir”)
printf(“dogru\n”);
else
printf(“yanlış”\n”);
...
Yukarıdaki kodun çalıştırılması durumunda büyük bir olasılıkla ekrana “yanlış” yazdırılacaktır.
Çünkü derleyicinin if parantezi içindeki karşılaştırma ifadesindeki iki “Ankara” stringinin
bellekte ayrı yerlere yerleştirilmesi durumunda, bu iki string birbirinden farklı iki ayrı char
türden adres olarak değerlendirilir. Benzer bir yanlışlık aşağıdaki kod parçasında da yapılmıştır.
char *str = “Mavi ay”;
char s[20];
printf(“parolayı giriniz : “);
gets(s);
if (str == s)
printf(“dogru parola\n”);
else
printf(“yanlış parola\n”);
s bir dizi ismidir ve derleyici tarafından dizinin yerleştirildiği bloğun başlangıcı olan char türden
bir adres sabiti gibi ele alınır. str ise char türden bir göstericidir. str göstericisine “Mavi ay”
stringi atandığında, derleyici önce “Mavi ay” stringini bellekte güvenli bir yere yerleştirir daha
sonra stringin yerleştirildiği yerin başlangıç adresini str göstericisine atar. programı kullananın
parola olarak “Mavi ay” girdiğini varsayalım. Bu durumda if deyimi içinde yalnızca s adresiyle
str göstericisinin içindeki adresin eşit olup olmadığı kontrol edilmektedir. Bu adresler de eşit
olmadıkları için ekrana "yanlış parola" yazılacaktır. İki yazının birbirine eşit olup olmadığı
strcmp fonksiyonu ile kontrol edilmeliydi:
char *str = “Mavi ay”;
char s[20];
printf(“parolayı giriniz : “);
gets(s);
if (!strcmp(str, s))
printf(“dogru parola\n”);
else
printf(“yanlış parola\n”);
Stringlerin Ömürleri
Stringler statik ömürlü nesnelerdir. Tıpkı global değişkenler gibi programın yüklenmesiyle
bellekte yer kaplamaya başlarlar, programın sonuna kadar bellekte kalırlar. Dolayısıyla stringler
çalışabilen kodu büyütür. Birçok sistemde statik verilerin toplam uzunluğunda belli bir
sınırlama söz konusudur.
Stringler derleyici tarafından .obj modüle linker tarafından da .exe dosyasına yazılırlar.
Programın yüklenmesiyle hayat kazanırlar.
Bir exe program 3 bölümden oluşmaktadır:
kod
data
stack
Kod bölümünde, fonksiyonların makine kodları vardır. Data bölümünde, statik ömürlü nesneler
bulunmaktadır, global değişkenler ve stringler bu bölümde bulunurlar. Stack bölümü yerel
değişkenlerin saklandığı bellek alanıdır. Stack bölümü her sistemde sınırlı bir alandır. Örneğin
DOS işletim sisteminde 64K büyüklüğünde bir stack söz konusudur. Yani hiç bir zaman yerel
değişkenlerin bellekte kapladığı alan 64K değerini geçemez. WINDOWS işletim sisteminde
default stack sınırı 1 MB’dır. Ancak bu sınır istenildiği kadar büyütülebilir.
Daha önceki derslerimizde, bir fonksiyonun yerel bir değişkenin adresi ile geri dönmemesi
gerektiğini söylemiştik. Çünkü fonksiyon sonlandığında yerel değişkenler bellekten boşaltılacağı
için, fonksiyonun geri döndürdüğü adres güvenli bir adres olmayacaktır. Örneğin aşağıdaki
program hatalıdır:
char *getname(void)
{
char s[100];
printf(“adı ve soyadı giriniz : “);
gets(s);
return s;
}
int main()
{
char *ptr;
ptr = getname();
puts(ptr);
return 0;
}
Fonksiyon bir stringin adresiyle geri dönebilir, bu durumda bir yanlışlık söz konusu
olmayacaktır. Çünkü stringler programın çalışma süresi boyunca bellektedir. Örneğin aşağıdaki
programda hatasız olarak çalışır:
char *getday(int day)
{
char *ptr;
switch (day) {
case 0: p = “Sunday”; break;
case 1: p = “Monday”; break;
case 2: p = “Tuesday”; break;
case 3: p = “Wednesday”; break;
case 4: p = “Thursday”; break;
case 5: p = “Friday”; break;
case 6: p = “Saturday”;
}
return p;
}
Stringlerin Birleştirilmesi
Daha önce söylendiği gibi stringler tek bir atom olarak ele alınmaktadır. Bir string aşağıdaki
gibi parçalanamaz:
char *ptr;
...
ptr = "sifir zero'nun C Dersi
Notlarını okuyorsunuz"; /* error */
Ancak string ifadeleri büyüdükçe bunu tek bir satırda yazmak zorlaşabilir. Ekrandaki bir satırlık
görüntüye sığmayan satırlar kaynak kodun okunabilirlirliğini bozar. Uzun stringlerin
parçalanmasına olanak vermek amacıyla C derleyicileri yanyana yazılan string ifadelerini
birleştirirler. Örneğin:
ptr = "sifir zero'nun C Dersi"
"Notlarını okuyorsunuz";
geçerli bir ifadedir. Bu durumda iki string birleştirilerecek ve aşağıdaki biçime getirilecektir.
ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz";
Derleyicinin iki stringi birleştirmesi için, stringlerin arasında hiçbir operatörün bulunmaması
gerekmektedir. :
p = "sifir " "zero";
ifadesi ile
p = "sifir zero";
ifadesi eşdeğerdir.
Birleştirmenin yanı sıra, bir ters bölü karakteri ile sonlandırılarak sonraki satıra geçiş
sağlanabilir. Örneğin:
ptr = "sifir zero'nun C Dersi \
Notlarını okuyorsunuz";
ifadesi ile
ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz";
ifadesi eşdeğerdir. Tersbölü işaretinden sonra string aşağıdaki satırın başından itibaren devam
etmelidir. Söz konusu string aşağıdaki şekilde yazılırsa:
ptr = "sifir zero'nun C Dersi \
Notlarını okuyorsunuz";
satır başındaki boşluk karakterleride stringe katılır ve sonuç aşağıdaki ifadeye eşdeğer olur:
ptr = "sifir zero'nun C Dersi Notlarını okuyorsunuz";
Ancak ters bölü karakteri ile sonraki satırın başından devam etme standart olarak her C
derleyicisinde geçerli olmayabilir. Çatışmalı bir durumla karşılaştığınızda çalıştığınız
derleyicilerin referans kitaplarına başvurmalısınız.
Stringlerde Ters Bölü Karakter Sabitlerinin Kullanılması
Stringler içerisinde ters bölü karakter sabitleri de (escape sequence) kullanılabilir. Derleyiciler
stringler içerisinde bir ters bölü karakteri gördüklerinde, onu yanındaki karakter ile birlikte tek
bir karakter olarak ele alırlar. Örneğin:
char *p;
p = "sifir\tzero";
ifadesinde \t bir karakterdir. (9 numaralı ASCII carakteri olan tab karakteri)
yani
printf("stringdeki karakter sayısı = %d\n", strlen(p));
ifadesi ile ekrana
stringdeki karakter sayısı = 11"
yazdırılacaktır.
String ifadelerinde doğrudan çift tırnak ya da ters bölü karakterleri kullanılamaz, çünkü bu
karakterlerin özek işlevi var. Çift tırnak karakter sabitinin kendisini ifade etmek için çift tirnak
karakterinden önce bir ters bölü karakteri kullanılır. Örneğin:
p = "\"sifir zero\"";
gibi bir string ifadesi geçerlidir. Bu durumda
puts(p);
ile ekrana
"sifir zero"
yazısı basılacaktır.
Stringlerle Göstericilere İlkdeğer Verilmesi
Stringler kullanılarak char türden göstericilere ilkdeğer verilebilir. Örneğin:
char *p = "İstanbul";
char *err = "Bellek yetersiz";
char *s = "Devam etmek için bir tuşa basınız";
...
String ifadeleri aslında char türden bir adres olduğuna göre ilk değer verilen göstericilerin de
char türden göstericiler olması gerekir. Dizilere iki tırnak içerisinde ilk değer vermeyle
göstericilere stringlerle ilk değer verme arasındaki ayırıma dikkat etmek gerekir.
char *p = "Deneme";
char s[10] = "Deneme";
ifadeleri tamamen farklıdır.
Göstericilere ilkdeğer verildiğinde derleyici bunu bir string ifadesi olarak ele almaktadır. Yani
string belleğe yerleştirildikten sonra başlangıç adresi göstericiye atanır. Oysa dizilerde önce dizi
için yer ayrılır, daha sonra karakterler tek tek dizi elemanlarına yerleştirilir. Dizilere ilkdeğer
verirken kullandığımız iki tırnak ifadeleri adres belirtmezler (string değildireler). Dizi
elemanlarına tek tek char türden sabitlerle ilk değer verme işlemi zahmetli olduğu için,
programcının işini kolaylaştırmak amacı ile dile bu ilk değer verme sentaksı eklenmiştir.
Yani
char s[10] = "Deneme";
aslında
char s[10] = {'D', 'e', 'n', 'e', 'm', 'e', '\0'};
ile aynı anlamdadır.
İcinde Yazı Tutan char Türden Dizilerle Bir Stringi Gösteren char Türden
Göstericilerin Karşılaştırılması
Şimdiye kadar almış olduğumuz bilgileri değerlendirdiğimizde, C dilinde bir yazıyı saklamak iki
ayrı şekilde sağlanabilir:
1. Yazıyı char türden bir dizi içinde saklamak:
...
char s1[100] = “sifir zero”;
char s2[100];
char s3[100];
gets(s2); / * Diziye yazının karakterleri klavyeden alınıyor */
strcpy(s2, s3); /* s3 dizisinin elemanları s2 dizisi içine kopyalanıyor */
2. Yazıyı bir string olarak saklayarak char türden bir göstericinin bu stringi göstermesini
sağlamak .
char *str = “sifir zero”;
İki yöntem birbirinin tamamen alternatifi değildir ve aşağıdaki noktalara dikkat edilmesi
gerekmektedir:
stringler statik nesneler oldukları için programın sonlanmasına kadar bellekte yer kaplarlar.
Yani bir göstericinin bir stringi gösterirken daha sonra başka bir stringi gösterir duruma
getirilmesi , daha önceki stringin bellekten silinmesi anlamına gelmeyecektir.
char *s = “Bu string programın sonuna kadar bellekte kalacak.”;
s = “artık yukarıdaki string ile bir bağlantımız kalmayacak...”;
Yazının char türden bir dizi içinde tutulması durumunda bu yazıyı değiştirmemiz mümkündür.
Dizi elemanlarına yeniden atamalar yaparak yazıyı istediğimiz gibi değiştirebiliriz ama
stringlerin değiştirilmesi taşınabilir bir özellik değildir. Stringler içinde bulunan yazılar
değiştirilmemelidir. Aşağıdaki kod parçaları taşınabilir değildir:
char *s = “Metin”;
char *str = “Ankara”;
s[1] = ‘Ç’;
strcpy(str, “Bolu”);
Derleyicilerin özdeş stringleri aynı adreste saklamaları konusunda bir garanti yok. Özdeş
stringler aynı adreste tek kopya olarak ya da farklı adreslerde tutuluyor olabilir. Daha önce
söylediğimiz gibi derleyiciler bu konuda birbirinden farklı davranabilirler. Yani yukarıdaki
örneklerde biz “Metin” ya da “Ankara” stringini değiştirdiğimizde, bu stringleri program içinde
baska yerlerde de kullanmışsak, bunların hepsinin değiştirilmiş olması gibi bir tehlike söz
konusudur.
char *s = "dosya açılamıyor"; /* birinci string */
char a[] = "dosya açılamıyor";
...
printf("%s", s);
...
printf("dosya açılamıyor"); /* ikinci string */
...
strcpy(s, "Dosya Açıldı");
...
"dosya açılamıyor" stringinin derleyici tarafından aynı adreste tek kopya olarak saklandığını
farsayalım. s adresinde bulunan stringe yeni bir yazı kopyalanınca hem birinci string hemde
ikinci string değişmiş olur.

Hiç yorum yok:

Yorum Gönder

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