Değişkenlerin ve bellekte yer kaplayan diğer nesnelerin tür bilgilerinden biraz bahsetmiştik. Bir nesnenin tür bilgisi o nesnenin kimliğidir. Bellekte bütün nesneler 0 ve 1 rakamlarından oluşan ikilik sistemde tutulur. Bu 0 ve 1’lerin belleğe nasıl yerleştirileceği ve okunurken nasıl yorumlanacağı tamamen tür bilgisine bakarak anlaşılır. Bu nedenle veri türü kavramı C’de ve diğer birçok programlama dilinde oldukça önemli bir yere sahiptir.
Bir önceki yazıda C’de nesnelerin ne olduğundan ve nelerin nesne belirttiğinden bahsetmiştim. C’de nesnelerin, fonksiyonların ve ifadelerin bir özelliği olan tür (type) veya diğer bir adıyla veri türü (data type), o nesnenin tuttuğu veya o ifadeden dönen ikilik sistemdeki verinin nasıl yorumlanacağına karar veren bir özelliktir. C’de türler çok çeşitli şekillerde sınıflandırılabilirler. Temel olarak burada 4 kategoriden bahsedebiliriz:
voidtürü- Temel türler
- Numaralandırılmış türler
- Türetilmiş türler
Tabii C’deki tür kategorilerini incelemek bu kadar kolay değildir. C standartlarında türler çok farklı kategoriler altında listelenmiş ve bazı kategorilerin altında yer alan türler iç içe geçmiştir. Bu nedenle önce C’de yer alan veri türlerinin sınıflandırılmasını kısaca incelememiz gerekir.
C’de Türlerin Sınıflandırılması
Öncelikle tür grupları kavramına girmeden, az önce verdiğim 4 kategori üzerinde C23 standartlarına kadar olan tüm türlerin kategorilerini ve alt kategorilerini listeleyelim:
voidTürü- Temel Türler
charTürü- İşaretli Tam Sayı Türleri
- Standart İşaretli Tam Sayı Türleri
signed charshortintlonglong long(C99)
- Bit Duyarlı İşaretli Tam Sayı Türleri (C23)
_BitInt(N)
- Genişletilmiş İşaretli Tam Sayı Türleri (C99)
- İmplementasyona göre değişir (
__int128gibi)
- İmplementasyona göre değişir (
- İşaretsiz Tam Sayı Türleri
- Standart İşaretsiz Tam Sayı Türleri
_Bool(C99)unsigned charunsigned shortunsigned intunsigned longunsigned long long(C99)
- Bit Duyarlı İşaretsiz Tam Sayı Türleri (C23)
unsigned _BitInt(N)
- Genişletilmiş İşaretsiz Tam Sayı Türleri (C99)
- İmplementasyona göre değişir (
__uint128gibi)
- İmplementasyona göre değişir (
- Kayan Noktalı Sayı Türleri
- Gerçek Kayan Noktalı Sayı Türleri
- Standart Kayan Noktalı Sayı Türleri
floatdoublelong double- Onluk Gerçek Kayan Noktalı Sayı Türleri (C23)
_Decimal32_Decimal64_Decimal128
- Karmaşık Türler (C99)
float _Complexdouble _Complexlong double _Complex
- Sanal Türler (C99)
float _Imaginarydouble _Imaginarylong double _Imaginary
- Numaralandırılmış Türler
- Türetilmiş Türler
- Dizi Türleri
- Yapı Türleri
- Birlik Türleri
- Fonksiyon Türleri
- Gösterici Türleri
- Atomik Türler (C11)
Gördüğünüz gibi C’de pek çok tür bulunmaktadır ve her yeni standartta daha fazla tür eklenmektedir. Ancak biz burada genellikle C99 öncesinde var olan temel türleri inceleyeceğiz. Diğerlerine yeri geldiğinde değineceğiz. Bu türlerden C99 standarları ile gelen long long, unsigned long long ve _Bool türlerine de yine bu yazıda değineceğiz. Ama bunu yapmadan önce tür gruplarına bakalım.
C’de tür grubu (type group), bu türlerin belli mantıksal kategoriler altında sınıflandırıldığı bir gruplama sistemidir. Tür grupları belli işlemlerin hangi türler ile yapılabileceğini belirtmek konusunda size yardımcı olur. Örneğin "bu işlemi sadece tam sayı türünden bir değişken ile yapabilirsiniz" dendiğinde, buradaki "tam sayı türünden değişken" belli bir tür grubunu temsil eder ve siz bu grubu bilirseniz hangi türlerin oraya uygun olduğunu da anlayabilirsiniz. Şimdi tür gruplarını ve içerdikleri türleri sıralayalım:
- Nesne Türleri (Object Types): Fonksiyon türü dışındaki tüm türleri ifade eder.
- Karakter Türleri (Character Types):
char,signed charveunsigned chartürlerini ifade eder. - Tam Sayı Türleri (Integer Types):
chartürünü, bütün işaretli tam sayı türlerini, bütün işaretsiz tam sayı türlerini ve numaralandırılmış türleri ifade eder. - Gerçek Türler (Real Types): Tüm tam sayı türlerini ve gerçek kayan noktalı sayı türlerini ifade eder.
- Artimetik Türler (Arithmetic Types): Tüm tam sayı türlerini ve kayan noktalı sayı türlerini ifade eder.
- Skaler Türler (Scalar Types): Artimetik türleri, gösterici türlerini ve C23 standartları ile birlikte
nullptr_ttürünü ifade eder. - Küme Türleri (Aggregate Types): Dizi türlerini ve yapı türlerini ifade eder.
- Türetilmiş Bildirici Türler (Derived Declarator Types): Dizi türlerini, fonksiyon türlerini ve gösterici türlerini ifade eder.
Burada şimdilik tür kategorilerini ve tür gruplarını özet olarak verdim. Ancak bu yazıda biz sadece gerçek türler ile ilgileneceğiz. Bunların da bazılarından bu yazıda bahsetmeyeceğiz, çünkü içerisinde C23 standardı ile gelecek olan türler de yer almaktadır. İlk olarak karakter türlerinden başlayalım.
Karakter Türleri
C’de bir değişkeni nasıl tanımlayacağımızı, ona ilk değer nasıl vereceğimizi ve daha sonra değerini nasıl değiştirip ekrana basacağımızı önceki yazılarda kısaca anlatmıştım. Şu ana kadar neredeyse hep int türü üzerinden gittik. Şimdi C’deki diğer veri türlerine bakma zamanı geldi. İlk olarak karakter türlerinden başlıyoruz. C’de şu anda resmi olarak 3 adet temel karakter veri türü bulunmaktadır:
charsigned charunsigned char
Bunları açıklamaya geçmeden önce signed ve unsigned anahtar kelimelerinden biraz bahsedelim. Bildiğiniz gibi bir tam sayı negatif, pozitif veya sıfır olabilir. Eğer veri türünün negatif tam sayı aralığından değerler de alabilmesini istiyorsak, bunu başına signed yazarak veya hiçbir şey yazmayarak belirtebiliriz. Ancak bir veri türünün 0 ile pozitif bir tam sayı arasında bir değer almasını istiyorsak başına genellikle unsigned anahtar kelimesini yerleştiririz. Bunu daha önceden gördüğümüz int veri türü üzerinde şu şekilde uygulayabiliriz:
int a = 5; // İşaretli
int b = -6; // İşaretli
signed int c = 10; // İşaretli
signed int d = -10; // İşaretli
unsigned int e = 0; // İşaretsiz
unsigned int f = 100; // İşaretsiz
Gördüğünüz gibi veri türünün başına herhangi bir şey yazmazsak veya signed yazarsak, bu veri türünün negatif sayıları kabul ettiği anlamına gelir ve bu tip veri türlerine işaretli (signed) veri türleri denir. Eğer başına unsigned getirirsek, bu veri türünün pozitif bir tam sayı veya sıfır değerini alabileceğini belirtmiş oluruz. Bu tip veri türlerine ise işaretsiz (unsigned) veri türü adı verilir. Bu anahtar kelimeler her türün başına getirilemezler, bunu zaten türleri incelerken farkedeceksinizdir. signed anahtar kelimesinin kullanımı ise çoğu zaman gereksizdir.
Bu anahtar kelimelerin ne anlama geldiğini öğrendikten sonra karakter veri türlerinden devam edelim. Bu açıklamadan sonra char veri türü ile signed char veri türlerinin aslında aynı türler olduğunu düşünebilirsiniz. Ancak durum böyle değildir. Bu üç karakter türü de birbirinden farklı türlerdir. Bu türlerin temel özelliği, programda tek bir karakteri tutabilecek genişlikte olmaları gerektiğidir. Peki hangi karakterler? İsterseniz karakter seti konusuna kısa bir bakış atalım.
İnsan dillerinde grafiksel bir gösterimi olan karakterlerin bilgisayarda depolanması, iletimi ve dönüşümü için onlara bir sayı atanması işlemine karakter kodlaması (character encoding) adı verilir.[1] Bu şekilde belli kodlama kurallarına göre bir araya gelen karakter topluluğuna ise karakter kümesi (character set) adı verilmektedir. Geçmişten günümüze kadar pek çok karakter kodlama standardı geliştirilmiştir. Bunlardan bazıları şöyledir:
- ASCII
- EBCDIC
- ISO 8859-1
- UTF-8
- UTF-16
- UTF-32
Bilgisayar dünyasında çok bilinen ASCII karakteri denilen karakterler, 0 ile 255 arasında sayılarla belirtilirler (aslında 0 ile 127, fakat artık genişletilmiş ASCII seti kullanılmaktadır). Hangi sayının hangi karaktere denk geldiğini şu şekilde internetin her köşesinde bulabileceğiniz bir tablo ile görebilirsiniz:
Yukarıdaki resimden anlayacağınız gibi ASCII karakter setinde A’dan Z’ye büyük ve küçük İngilizce harfler, 0’dan 9’a rakamlar, noktalama işaretleri, kontrol karakterleri ve daha birçok özel karakter bulunmaktadır. char veri türü bu karakter kümesinin bir alt kümesi olan "Basic Execution Character Set" isimli karakter kümesini tutabilecek genişlikte olmalıdır (yani en az 1 byte). Bu karakter seti ile ilgili ayrıntılı bilgiye buradan erişebilirsiniz.
Peki ben char türünden bir değişkene herhangi bir karakter atamak istediğimde her zaman bu tablodan değerine bakarak mı değer atayacağım? Elbette hayır. C’de tam sayı değişmezleri gibi karakter değişmezleri de yer almaktadır (aslında C’de bunlara tam olarak değişmez denmez ama ben şimdilik bu yazıda böyle diyeceğim). Değişmezler konusuna bir sonraki yazıda ayrıntılı olarak girecek olsam da karakter değişmezlerinin tek tırnak içerisine yazılan karakterler şeklinde ifade edildiğini söyleyebilirim:
char test = 'A'; // test = 65
char test2 = '1'; // test2 = 49
İşte bu şekilde kod içerisinde karakterleri doğrudan kullanabilmeniz mümkündür. Bunu char türünden bir yere atadığınızda, otomatik olarak tek tırnak içerisine yazdığınız karakterin ASCII karşılığı oraya atanacaktır.
char türünün en az 1 byte olması gerektiğini söylemiştik. Ancak esas sorun bunun işaretli mi yoksa işaretsiz mi olacağıdır. Bilgisayar belleğinde 1 byte’lık yer kaplayan bir veriyi işaretli olarak yorumlamak istersek "-128" ile "127" arasında, işaretsiz olarak yorumlamak istersek de "0" ile "255" arasında bir sayı elde ederiz. İşte signed char ve unsigned char türleri, aslında bu verinin nasıl yorumlanacağını garanti eder. Karakter için tutulan verilerde genellikle "0" ile "127" arası yeterli iken, ikilik ham veri tutmada genellikle "0" ile "255" arası tercih edilir.
signed char a = 65;
signed char b = -100;
unsigned char c = 0;
unsigned char d = 200;
char türünün arkaplanda signed char veya unsigned char olarak işlem göreceği implementasyona göre değişen (implementation-defined) bir durumdur. Burada genel kural şudur: Doğrudan karakterler ile işlem yapmak istiyorsanız char, ikilik ham veriler ile işlem yapmak istiyorsanız unsigned char, sadece "-128" ile "127" arasında değer tutacak bir veri yapısı arıyorsanız signed char kullanmanız daha doğru bir tercih olacaktır. Yine yukarıdaki örnekte bu tür değişkenlere sadece karakter değişmezleri değil, normal tam sayı değişmezleri ile de değer verebiliriz. Ancak bu değerlerin aralıkları aşması durumu daha sonra ele alınacaktır.
Tam Sayı Türleri
C’de tam sayı türünden verileri tutmak için kullanılan türlere tam sayı türü (integer type) adı verilmektedir. Tam sayı türleri işaretli (negatif ve pozitif) veya işaretsiz (sadece pozitif) olabilmektedir. Negatif, pozitif tam sayıları ve sıfır sayısını alabilen türlere işaretli tam sayı türü (signed integer type), sadece pozitif tam sayıları ve sıfır sayısını alabilen türlere ise işaretsiz tam sayı türü (unsigned integer type) adı verilmektedir. Yine bunların her biri de 3 kategoriye ayrılmaktadır:
- Standart Tam Sayı Türleri
- Bit Duyarlı Tam Sayı Türleri
- Genişletilmiş Tam Sayı Türleri
C23 standardı ile dile eklenecek olan bit duyarlı tam sayı türlerini (bit-precise integer types) ve C99 ile dile eklenmiş, ancak içeriği implementasyona göre değişen genişletilmiş tam sayı türlerini (extended integer types) burada anlatmayacağım. Burada sadece belli aralıklardaki tam sayıları tutmaya yarayan standart tam sayı türlerini (standard integer types) anlatacağım. Standart tam sayı türleri işaretli veya işaretsiz olabilmektedir. Bu türlerin birbirlerinden tek farkı her birinin farklı uzunluktaki tam sayıları tutabilmesidir.
Standart işaretsiz tam sayı türleri sadece sıfırdan belli bir aralığa kadar olan pozitif tam sayıları alabilmektedir. Bu aralık onların bit genişliğine bağlıdır. Bit genişliği ikilik tabandaki bir sayının kaç bitlik olduğunu bildiren bir değerdir. Örneğin 8 bitlik bir alanın bit genişliği de 8’dir (bu konu bu kadar basit olmasa da şimdilik böyle kabul edelim). 8 bitlik bir alanda tutulabilecek tam sayılar sınırlıdır.
Yine standart işaretli tam sayı türleri, bit genişliklerinin izin verdiği ölçüde pozitif ve negatif tam sayıları tutabilirler. Bu türlerin boyutları hakkında birazdan daha ayrıntılı konuşacağız. İsterseniz bu tam sayı türlerini listeleyelim:
- Standart İşaretli Tam Sayı Türleri
signed charshort intintlong intlong long int
- Standart İşaretsiz Tam Sayı Türleri
_Boolunsigned charunsigned short intunsigned intunsigned long intunsigned long long int
long long int ve unsigned long long int türleri daha büyük tam sayıları tutabilmek amacıyla dile C99 standartları ile eklenmiştir. _Bool türü ise mantıksal olarak doğru (true) veya yanlış (false) değerini tutabilecek genişlikte olup dile yine C99 standardı ile eklenmiştir. Ancak _Bool türünden burada değil, bir diğer alt başlık altında bahsedeceğim. Diğer türler ise C dilinin ilk yıllarından beri vardır.
Daha önce de söylediğim gibi işaretli tam sayı türlerinin başına signed anahtar kelimesini de yazabilirsiniz, ancak bu çoğu durum için gereksizdir. Sadece char türünün başına yazılan signed anlamlı olabilir. İşaretsiz tam sayı türlerinde ise başa unsigned anahtar kelimesini yazmanız gerekir (_Bool hariç). İşaretli tam sayı türlerinin değişken tanımlamalarında kullanılması ile ilgili örnekler verelim:
signed char a = -10;
short int b = 100;
short c = 100;
int d = 600;
long int e = -900;
long f = 900;
long long int g = 100000;
long long h = 100000;
Burada b ile c değişkenleri (short int), e ile f değişkenleri (long int) ve g ile h değişkenleri (long long int) aynı türdendir. Buradan da anlayacağınız gibi C’de yer alan tam sayı türlerinde çok çeşitli alternatif yazımlar olabilir. Bunları bir tabloda vermeden önce işaretsiz tam sayı türleri ile ilgili birkaç örnek verelim:
unsigned char a = 10;
unsigned short int b = 200;
unsigned short c = 200;
unsigned int d = 600;
unsigned long int e = 900;
unsigned long f = 900;
unsigned long long int g = 100000;
unsigned long long h = 100000;
Yine burada da b ile c değişkenleri (unsigned short int), e ile f değişkenleri (unsigned long int) ve g ile h değişkenleri (unsigned long long int) aynı türdendir. Şimdi tam sayı türlerinin çeşitli eş değer yazımlarını bir tabloda gösterelim:
| Veri Türü | Eş Değer Tür | Tercih Edilen Yazım |
|---|---|---|
| char | char | char |
| signed char | signed char | signed char |
| unsigned char | unsigned char | unsigned char |
| short | short int | short |
| short int | ||
| signed short | ||
| signed short int | ||
| unsigned short | unsigned short int | unsigned short |
| unsigned short int | ||
| int | int | int |
| signed | ||
| signed int | ||
| unsigned | unsigned int | unsigned int |
| unsigned int | ||
| long | long int | long |
| long int | ||
| signed long | ||
| signed long int | ||
| unsigned long | unsigned long int | unsigned long |
| unsigned long int | ||
| long long | long long int | long long |
| long long int | ||
| signed long long | ||
| signed long long int | ||
| unsigned long long | unsigned long long int | unsigned long long |
| unsigned long long int |
Çok uzun ve karmaşık görünen bir liste oldu. Şimdi buradan yapmamız gereken çıkarımları şu şekilde sıralayalım:
signedveunsignedanahtar kelimeleri veri türünün tutacağı değerin işaretli veya işaretsiz olabileceğini belirtmektedir.- C’de bulunan
signedanahtar kelimesinin pek bir kullanım alanı yoktur (charile kullanımı hariç). Bazen okunabilirliği arttırmak için kullanılır. Tek başına kullanıldığında iseinttürünü ifade eder. - C’de bulunan
unsignedanahtar kelimesi tek başına kullanıldığındaunsigned inttürünü ifade eder. short int,long int,long long inttürlerindeintanahtar kelimesini yazmaya gerek yoktur. Bu durum bu türlerinunsignedversiyonları için de geçerlidir.chartürünü işaret belirtmeden tam sayı işlemlerinde kullanmak tehlikelidir. Bu nedenle tam sayı işlemlerinde başınasignedveyaunsignedanahtar kelimelerinden birinin getirilmesi önerilir.char,signed charveunsigned charüç farklı türü ifade eder.
Gerçek Kayan Noktalı Sayı Türleri
Şu ana kadar gördüğümüz türler bilgisayarda tam sayıları tutmak ile ilişkili türlerdi. Peki tam sayılar kümesinin üstünde yer alan sayıları (gerçek sayılar gibi) bilgisayarda nasıl tutabiliriz? Bu bir sayıyı ikilik sisteme dönüştürmek kadar kolay bir iş değildir. Bilgisayar dünyasında ikilik düzende tutulan gerçek sayılara kayan noktalı sayı (floating-point number) adı verilmektedir. Matematikteki gerçek sayılar sonsuza kadar giderken, bilgisayar dünyasında bu sayılar belli bir limite sahiptir. Arasındaki kavram farkı da buradan gelir.
Gerçek sayıların bilgisayarda kayan noktalı sayıya dönüştürülmesi ile ilgili en bilinen standart IEEE 754 isimli standarttır. Bu standardın ilk versiyonu 1985 yılında IEEE tarafından yayımlanmıştır. Yıllar geçtikçe çok çeşitli geliştirmeler ve genişletmeler yapılıp günümüzde en çok kullanılan kayan noktalı sayı aritmetiği standardı haline gelmiştir. Standardın içerisinde çok çeşitli bit genişliklerinde ve türlerde kayan noktalı sayıların tutulması ile ilgili bilgiler yer almaktadır.
Bu standart, sayıların gösterimi ile ilgili iki kısma ayrılır: İkilik (Binary) ve Onluk (Decimal). Bunların her birinin tutabileceği bit sayılarına göre (32-bit, 64-bit vb.) ayrı ayrı kısımları yer almaktadır. Ancak ben burada bunların ayrıntısına girmeyeceğim. Sadece şunu bilmenizde fayda var: C’de standart kayan noktalı sayı türleri (standard floating-point types) adı verilen türler ikilik gösterimi, C23 ile gelecek olan ve onluk gerçek kayan noktalı sayı türleri (decimal real floating-point types) adı verilen türler ise onluk gösterimi ifade etmektedir.
Bu bölümde ben sadece standart kayan noktalı sayı türlerinden bahsedeceğim. Diğer kayan noktalı sayı türlerine başka yazılarda değinmeyi düşünüyorum. C’de yer alan standart kayan noktalı sayı türleri şunlardır:
floatdoublelong double
float türü IEEE 754 standardında binary32 isimli formata denk gelmektedir ve tek duyarlı kayan noktalı sayı türü (single precision floating-point type) olarak adlandırılmaktadır. double türü IEEE 754 standardında binary64 isimli formata denk gelmektedir ve çift duyarlı kayan noktalı sayı türü (double precision floating-point type) olarak adlandırılmaktadır. long double türü ise IEEE 754 standardında binary128 isimli formata, o desteklenmiyorsa binary64-extended isimli formata denk gelmektedir ve genişletilmiş duyarlı kayan noktalı sayı türü (extended precision floating-point type) olarak adlandırılmaktadır. C’de bu türlerle ilgili birkaç değişken tanımlayalım:
float a = 10.9;
double b = -90.78;
long double c = 1000.121212;
Burada kayan noktalı sayı türünden değişkenlere verilen çeşitli değerleri görmektesiniz. Tam sayı türlerinde yazılan değişmezlerin aksine, burada ondalıklı sayı olarak yazım görülmektedir. Yani kayan noktalı sayı değişmezlerini nokta (.) ile tam ve ondalık kısımlarını ayırarak yazmanız mümkündür. Burada nokta (.) yerine virgül (,) kullanmak, en sık yapılan hatalardan biridir. Değişmezler ile ilgili daha sonraki yazılarda daha ayrıntılı konuşacağız.
Kayan noktalı sayı türlerinin signed ve unsigned versiyonları bulunmamakta, hepsi işaretli olarak kabul edilmektedir. Genel olarak float türünün 32-bit genişliğinde, double türünün 64-bit genişliğinde kayan noktalı sayıları tutabildiğini söyleyebiliriz. long double türü ise genellikle 80-bit veya 128-bit genişliğinde kayan noktalı sayıları tutabilmektedir. Bunun tam bir standardı yoktur ancak long double türü en az double kadar geniş olmalıdır. Bu türlerin alabileceği değerler ve bellek yerleşimleri ile ilgili birazdan konuşacağız.
_Bool Veri Türü
Eğer statik türler içeren diğer programlama dilleri ile çalıştıysanız, bu programlama dillerinde genellikle mantıksal doğru veya yanlış olma durumunu tutabilecek veri türleri ile karşılaşmışsınızdır. C’nin ilk yıllarında böyle bir türe ihtiyaç duyulmamıştır. Doğruluk ve yanlışlık durumu tam sayılarla (0 ve 1 veya 0 ve 0 dışı gibi) ifade edilmiştir. Hatta bazı C kodları doğrudan bu tam sayı mantığı üzerine kurulmuştur. Ancak mantıksal doğru ve yanlışı tutan türler yaygınlaştıkça C’de bu tip bir veri türünün eklenmesine ihtiyaç duyulmuştur.
C++’ta yer alan bool veri türü zaten bu amaçla kullanılmaktadır. Ancak C99 standartlarına kadar C’de bu işi yapacak herhangi bir veri türü yoktu. C99 ile dile _Bool anahtar kelimesi eklenmiştir. Bu şekilde mantıksal durumların daha düzgün ele alınması amaçlanmıştır. Ancak iş bu tür ile bitmemiş bir de <stdbool.h> başlık dosyası ile dile _Bool türünün yerini tutan bool isimli makro, değeri 1 olan true isimli sabit ve değeri 0 olan false isimli sabit de eklenmiştir. Böylelikle _Bool türünü şu şekilde kullanabiliriz:
#include <stdbool.h>
_Bool test = 0;
_Bool dogru = true;
_Bool yanlis = false;
bool test2 = true;
Elbette koda <stdbool.h> başlık dosyasını dahil etmezseniz bool makrosunu ve true ile false sabitlerini kullanamazsınız. Bunları kullanamasanız bile _Bool türünden değişkene 0 ve 1 ile değer verebilirsiniz (test isimli değişkende yaptığımız gibi). C23 standartları ile bu durum değişecektir. _Bool anahtar kelimesi silinip yerine bool, false ve true anahtar kelimeleri getirilecek, ayrıca <stdbool.h> başlık dosyası içindeki bool, false ve true tanımları kaldırılacaktır. Yani herhangi bir başlık dosyası dahil etmeden şu kullanım mümkün olacaktır:
// C23
bool dogru = true;
bool yanlis = false;
Ancak şimdilik C99’daki kullanımından gitmemiz daha doğrudur. _Bool türü iki değer olan doğru (true) ve yanlış (false) değerlerini tutabilecek büyüklükte olmalıdır. Genel olarak _Bool türü (ve C23’te yerini alacak bool türü) işaretsiz tam sayı türü olarak kabul edilmektedir. Ona 0 verildiğinde yanlış, 0 dışında herhangi bir değer verildiğinde ise doğru kabul edilir.
Veri Modelleri ve Türlerin Değer Aralıkları
C standartları genellikle türlerin boyutları ile ilgili net bir şey söylemez. Ancak olması gereken mininmum boyutları veya tutabilecekleri değerleri belirten ifadeleri kullanır. Bunun sebebi C dilinin derleyici ve işletim sistemi implementasyonlarına göre değişiklik gösterebilmesidir. Bazı sistemlerde küçük boyutlar yeterli iken bazılarında ise daha büyük boyutlara ihtiyaç duyulabilir. Bu nedenle C’deki veri türlerinin boyutlarının implementasyona bağlı olduğunu söyleyebiliriz. Ancak yine de aritmetik türler arasında şu şekilde minimum bir ilişki vardır:
1 byte ≤ char ≤ short ≤ int ≤ long ≤ long long
Bu ilişkiye göre hepsinin boyutu 1 byte da olabilir bundan büyük de olabilir. Ancak örneğin int türünün 4 byte olduğu bir sistemde short türü 8 byte olamaz veya long türü 2 byte olamaz. Bunlar genel kabul görmüş kurallardır. Genellikle işletim sistemlerinde veri modeli (data model) denilen bazı standartlara uyulur. Yaygın kullanılan veri modelleri şunlardır:
- LP32 (long 32-bit, pointer 32-bit): Win16 sistemlerinde kullanılan eski bir veri modelidir.
- ILP32 (int 32-bit, long 32-bit, pointer 32-bit): 32-bit Windows, GNU/Linux ve OSX işletim sistemlerinde kullanılan oldukça yaygın bir veri modelidir.
- LLP64 (long long 64-bit, pointer 64-bit): 64-bit Windows sistemlerinde kullanılan veri modelidir.
- LP64 (long 64-bit, pointer 64-bit): 64-bit OSX ve GNU/Linux işletim sistemlerinde kullanılan veri modelidir.
Şimdi bu veri modellerine göre C’deki işaretli ve işaretsiz standart tam sayı türlerinin (_Bool türü hariç) bit genişliklerini inceleyelim:
| Veri Türü | Eş Değer Tür | Veri Modelinin Bit Türünden Genişliği | |||
|---|---|---|---|---|---|
| LP32 | ILP32 | LLP64 | LP64 | ||
| char | char | 8 | 8 | 8 | 8 |
| signed char | signed char | ||||
| unsigned char | unsigned char | ||||
| short | short int | 16 | 16 | 16 | 16 |
| short int | |||||
| signed short | |||||
| signed short int | |||||
| unsigned short | unsigned short int | ||||
| unsigned short int | |||||
| int | int | 16 | 32 | 32 | 32 |
| signed | |||||
| signed int | |||||
| unsigned | unsigned int | ||||
| unsigned int | |||||
| long | long int | 32 | 32 | 32 | 64 |
| long int | |||||
| signed long | |||||
| signed long int | |||||
| unsigned long | unsigned long int | ||||
| unsigned long int | |||||
| long long | long long int | 64 | 64 | 64 | 64 |
| long long int | |||||
| signed long long | |||||
| signed long long int | |||||
| unsigned long long | unsigned long long int | ||||
| unsigned long long int | |||||
Peki bit genişliği bilgisi tam olarak ne ifade etmektedir? Bit genişlikleri bir türün alabileceği değer aralığını hesaplamamızı sağlar. "N" bit genişliğini ifade etmek üzere işaretsiz bir tam sayının alabileceği minimum değer "0", maksimum değer ise "2N-1" olabilmektedir. İşaretli bir tam sayı türünün ise alabileceği minimum değer "-(2N-1-1)", maksimum değer ise "2N-1-1" olabilir.
Ancak C23 standartlarından sonra işaretli tam sayıların değer aralığı "-2N-1" ile "2N-1" arasında garanti edilecektir. Çünkü eski aralık değeri negatif sayıların ikilik tabanda bire tümleme (one’s complement) ve işaret ve genlik (sign-and-magnitude) yöntemlerini kullanarak tutulmasına olanak sağlamaktaydı. Ancak günümüzde çoğu sistem ikiye tümleme (two’s complement) yöntemini kullanarak negatif sayıları tuttuğu için yeni standart bu yönteme uygun olacaktır. İkiye tümleme yöntemini birazdan daha detaylı göreceksiniz.
Son olarak kayan noktalı sayı veri türlerinin değer aralıklarından biraz bahsedelim. Kayan noktalı sayı türlerinde float genellikle 32-bit genişliğinde, double 64-bit genişliğinde ve long double ise 128-bit (veya 80-bit) genişliğinde implement edilmektedir. Kayan noktalı sayı türlerinin veri modelleri ile pek bir alakası yoktur. Daha çok o anda üzerinde koşulan mimarinin desteklediği standartlara göre şekillenirler. Ancak çoğu mimaride az önce bahsettiğim bit genişlikleri kullanılır.
Şimdi 2 veri modelini ele alıp şu ana kadar incelediğimiz türlerin değer aralıklarını bir tabloya dökelim. Bu tablodaki ilk veri modelini LP32 olarak, ikinci veri modelini ise LP64 olarak ele alalım. Ayrıca ilk senaryoda long double türü 10-byte, ikinci senaryoda ise 16-byte olsun. Bu verilere göre şu şekilde bir değer aralığı tablosu ortaya çıkacaktır:
| Veri Türü | LP32 Senaryosu | LP64 Senaryosu | ||
|---|---|---|---|---|
| Boyut (byte) | Aralık | Boyut (byte) | Aralık | |
| char | 1 | -128 – 127 | 1 | -128 – 127 |
| unsigned char | 1 | 0 – 255 | 1 | 0 – 255 |
| short | 2 | -32.768 – 32.767 | 2 | -32.768 – 32.767 |
| unsigned short | 2 | 0 – 65.535 | 2 | 0 – 65.535 |
| int | 2 | -32.768 – 32.767 | 4 | -2.147.483.648 – 2.147.483.647 |
| unsigned int | 2 | 0 – 65.535 | 4 | 0 – 4.294.967.295 |
| long | 4 | -2.147.483.648 – 2.147.483.647 | 8 | -9.223.372.036.854.775.808 – 9.223.372.036.854.775.807 |
| unsigned long | 4 | 0 – 4.294.967.295 | 8 | 0 – 18.446.744.073.709.551.615 |
| long long | 8 | -9.223.372.036.854.775.808 – 9.223.372.036.854.775.807 | 8 | -9.223.372.036.854.775.808 – 9.223.372.036.854.775.807 |
| unsigned long long | 8 | 0 – 18.446.744.073.709.551.615 | 8 | 0 – 18.446.744.073.709.551.615 |
| float | 4 | -3,4×10+38 – 3,4×10+38 | 4 | -3,4×10+38 – 3,4×10+38 |
| double | 8 | -1,8×10+308 – 1,8×10+308 | 8 | -1,8×10+308 – 1,8×10+308 |
| long double | 10 | -1,2×10+4932 – 1,2×10+4932 | 16 | |
Özellikle kayan noktalı sayı türlerinin değerleri değişiklik gösterebilir. Çünkü bu değerlerdeki minimum ve maksimum kavramları tam sayılar kadar basit değildir. Ancak burada genel olarak bir türün bit genişliği ile onun tutabileceği değer aralıklarının ilişkisini anlamanız yeterlidir.
Türlerin Bellekte Yerleşimi ve Yorumlanması
C’deki temel türlerden, türlerin alabilecekleri değerlerden, değer aralıklarından ve türlerin boyutlarından konuştuk. Şimdi de son olarak türlerin bellekte nasıl yerleştikleri ile ilgili genel açıklamaları yapıp yazıyı sonlandıralım. Genel olarak RAM bellekten bahsedecek olursak, RAM bellek mantıksal olarak 1 byte boyutundaki hücrelerden meydana gelir. Her bir hücreye karşılık gelen bir adres numarası vardır. 1 byte uzunluğunda bir veri 1 hücreye, 2 byte uzunluğundaki veri 2 hücreye karşılık gelir ve bu şekilde gider. Bu hücreler temsili olarak aşağıdaki figürdeki gibi kutularla gösterilir:
Elbette 1 byte’dan daha büyük veriler söz konusu olduğunda aklınıza ilk gelen şey; örneğin 2 byte’lık bir veri için ilk byte’ının en düşük adrese, ikinci byte’ının da bir sonraki adrese yazılması olabilir. Ancak tüm işlemciler verileri bu şekilde belleğe işlemezler. İşlemcilerin verileri hangi sırada belleğe yerleştireceğini ve okuyacağını belirten bu özelliğine Endianness adı verilmektedir.
İki tür Endianness vardır: Little-Endian ve Big-Endian. Little-Endian’da düşük anlamlı byte düşük adrese denk gelirken, Big-Endian’da düşük anlamlı byte yüksek adrese denk gelir. Çoğu işlemci mimarisi (Intel x86 vb.) genellikle Little-Endian şemasını kullanır. Bunun hakkında daha fazla ayrıntıya girmeyeceğim. Aslında şu figürden olayı tamamen anlayabilirsiniz:
Yukarıdaki resimde sayılar 16’lık tabana çevrilip işlem yapılmıştır. Ayrıca a harfi bellek hücresinin adresini belirtmektedir. Bellek gösterimini ve Endianness kavramını kısaca açıkladıktan sonra esas konumuza geçebiliriz. Öncelikle farklı türden 4 değişken tanımlayalım. Bunları LP64 veri modelini kullanan bir sistemde yazdığımızı düşünelim:
char karakter = 'A';
short kucuk_sayi = -4;
unsigned int sayi = 15;
float pi_sayisi = 3.14;
Şimdi bu 4 değişkenin belleğe nasıl yerleştirileceğini inceleyelim. Öncelikle en basit olan karakter isimli değişkenden başlayalım. Bu değişkenin türü char olduğundan, bellekte 8 bitlik yer kaplamaktadır. Değeri ise A, yani ASCII tablosunda 65 sayısına denk gelmektedir. 65 sayısını ikilik tabana çevirdiğimizde 7 bitlik 100 0001 sayısını buluruz. char türünü işaretli kabul edersek, en baştaki bit işaret biti olacak ve sayı pozitif olduğu için 0 alacağız. Böylelikle elimizde 0100 0001 verisi olacaktır. Belleğin her bir hücresi 8 bit kabul edildiğine göre bu veri sorunsuz bir şekilde tek bir hücreye sığacaktır.
Şimdi de short türünde olan kucuk_sayi değişkenimize bakalım. Bu değişkenin bellekte kapladığı alan 2 byte, yani 16 bittir. Sistemin Little-Endian desteklediğini düşünürsek, kucuk_sayi değişkenimizi ikilik tabana çevirip işlemci mimarimize göre bellek hücremize yerleştirmemiz gerekir. Normal bir pozitif tam sayıyı ikilik tabana çevirmeyi biliyoruz. Peki negatif sayılar? Bunun için genellikle daha önce de bahsettiğim şu 3 metottan biri kullanılmaktadır:
- Bire tümleme (One’s complement)
- İşaret ve genlik (Sign-and-magnitude)
- İkiye tümleme (Two’s complement)
Çoğu sistemde ikiye tümleme algoritması kullanılmaktadır. İkiye tümleme algoritması şu şekilde çalışmaktadır:
- Öncelikle sayının pozitif halini ikilik tabana çevirin.
- Daha sonra bütün bitleri 1 ise 0, 0 ise 1’e çevirin.
- Çıkan ikilik tabandaki sayıya 1 ekleyin.
İşaretli tam sayılarda en baştaki bite işaret biti (sign bit) adı verilmektedir. İşaret biti 1 olduğunda bu sayının negatif, 0 olduğunda ise sayının pozitif olduğu anlaşılır. İşaresiz tam sayılarda ise böyle bir bit yoktur ve işaretli sayıya çevirme prosedürü uygulanmaz. Şimdi kucuk_sayi değişkeninin değerini bu yöntem ile 16 bitlik alanda ikilk tabana çevirelim:
- Öncelikle
-4sayısının pozitif hali olan4sayısının ikilik tabandaki karşılığı100olur. Bunu 16 bit olarak yazarsak:0000 0000 0000 0100 - Şimdi tüm bitleri tersine çevirelim:
1111 1111 1111 1011 - Son olarak da bu sayıya 1 ekleyelim:
1111 1111 1111 1100
Gördüğünüz gibi -4 sayısının ikilik tabandaki karşılığı ikiye tümleme algoritması kullanılarak 1111 1111 1111 1100 çıkmaktadır. Taban aritmetiği ve ikiye tümleme konusunda kapsam dışına çıkmamak için daha fazla ayrıntıya girmeyeceğim. sayi değişkenini ise doğrudan ikilik tabana 32 bitlik şekilde çeviririrsek sonuç şu olacaktır: 0000 0000 0000 0000 0000 0000 0000 1111. float türünden olan pi_sayisi değişkeni ise IEEE 754 standartlarına göre ikilik tabana çevrilmektedir. Ancak bunun ayrıntısı bu yazıda anlatılmayacaktır.
Gerekli taban dönüşümleri yapıldıktan sonra düşük anlamlı değerler düşük adresten başlatılarak (resimdeki A adres numarasını belirtmektedir), yani Little-Endian kullanıldığını kabul ettiğimiz bir sistemde şu şekilde belleğe yazılır:
Bu uzun ve yorucu bölümümüzün sonuna geldik. Çok fazla uzadığı için örnek yapmayı sonraki bölümlere bıraktım. Bu konunun ayrıntıları önemli bir yere sahip olduğundan, konuyu ayrı bir bölüme taşımayı uygun buldum. İleride bunlarla ilgili daha fazla örnekle karşılaşacaksınız. Ayrıca veri türlerinin boyutunu ilgilendiren bir uygulama yazarken hangi yolları izlemeniz gerektiğini de öğreneceksiniz.






