Veri tipleri II: record, union, pointer ve tip eşdeğerliği
Record ve tuple, free ve discriminated union, pointer/reference, dangling pointer ve memory leak, heap yönetimi, type checking, strong typing ve name/structure type equivalence.
Önceki sayfa tek tek değerleri tutan tiplerle ilgiliydi. Bu sayfa değerleri bir araya getiren tipler (record, union), başka değerleri gösteren tipler (pointer, reference) ve tiplerin birbiriyle uyumunu (type checking, type equivalence) kapsar. Sınavda en çok sorulanlar: dangling pointer ile memory leak farkı, free ve discriminated union, name ve structure equivalence.
Altın kurallar:
- Dangling pointer: silinmiş hücreyi gösteren pointer. Leak: kimsenin göstermediği hücre.
- Free union kontrol edilmez, discriminated union bir tag taşır.
- Name equivalence = aynı ad, structure equivalence = aynı yapı.
- Reference counting döngüleri toplayamaz.
Record
Record, heterojen elemanların (field) adlarıyla erişildiği topluluktur. Dizinin aksine elemanlar farklı tiplerde olabilir; erişim indisle değil adla olur: emp.name. COBOL ve C struct, Java’da sınıflar ve record.
record P(int x, int y) {}
P p = new P(1, 2), q = new P(1, 2);
System.out.println(p.equals(q) + " " + (p == q));
System.out.println(p);
true false
P[x=1, y=2]
Alan erişimi derleme zamanında sabit bir ofsete çevrilir, bu yüzden hızlıdır. Tuple record’a benzer ama alanların adı yoktur (Python (3, "a"), ML); erişim konumla olur.
Union
Union, bir değişkenin çalışma boyunca farklı zamanlarda farklı tiplerde değer tutabildiği tiptir. Tüm alanlar aynı belleği paylaşır.
- Free union (C, C++): hangi alanın geçerli olduğunu takip eden bir şey yok. Tip kontrolü yapılmaz.
- Discriminated union (Ada, ML, Haskell): bir tag (discriminant) alanı hangi tipin geçerli olduğunu söyler; yanlış alan okunursa hata.
union { int i; float f; } u;
u.i = 1;
printf("%g", u.f); /* int'in bitleri float olarak okunur */
Aynı bitlerin float yorumu Java ile gösterilebilir:
System.out.println(Float.intBitsToFloat(1));
System.out.println(Float.intBitsToFloat(1065353216));
System.out.println(Float.floatToIntBits(1.0f));
1.4E-45
1.0
1065353216
1 tam sayısı float olarak 1.4E-45’tir; 1.0f’nin bitleri ise int olarak 1065353216’dır. Free union bu karışıklığı sessizce yapar. Java ve C# union’ı hiç almamıştır; reliability için kalıtım (inheritance) kullanılır.
Pointer ve reference
Pointer bir bellek adresi ya da özel değer nil (null) tutar. İki kullanımı var: dolaylı adresleme ve heap’te dinamik bellek yönetimi.
| İşlem | C/C++ | Anlamı |
|---|---|---|
| Atama | p = &x; | p, x’in adresini tutar |
| Dereference | *p | p’nin gösterdiği hücredeki değer |
| Aritmetik | p + 1 | Bir sonraki eleman; adres sizeof(tip) kadar artar |
| Alan erişimi | p->x | (*p).x |
int a[5] = {10, 20, 30, 40, 50};
int *p = a; /* p = &a[0] */
p += 2; /* p = &a[2] */
/* *p + 1 = 31, *(p + 1) = 40, p - a = 2 */
Reference tipi (C++ int&, Java ve C# sınıf değişkenleri) bir nesneyi gösterir ama aritmetik yapılmaz. Java’da reference’lar hiçbir zaman serbest bırakılmış belleği göstermez, çünkü serbest bırakma garbage collector’ın işidir. Bu yüzden Java’da dangling reference yoktur.
Pointer problemleri
Dangling pointer
Serbest bırakılmış (deallocate edilmiş) bir heap hücresinin adresini hâlâ tutan pointer.
int *p = new int(5);
int *q = p;
delete p; // p ve q artık serbest bırakılmış hücreyi gösteriyor
p’yi nullptr yapabilirsin, ama q’yu unutursun. O hücre daha sonra başka bir değişkene verilirse, q üzerinden yazmak başka bir verinin üzerine yazar.
Lost heap-dynamic variable (memory leak)
Ayrılmış ama artık hiçbir pointer’ın erişemediği heap hücresi. Bunlara garbage denir.
int *p = new int; // hücre 1
p = new int; // hücre 2; hücre 1 KAYBOLDU
int *q = p; // q hücre 2'yi gösteriyor
p = new int; // hücre 3; hücre 2 q sayesinde kurtuldu
Kayıp hücre sayısı 1. Sınavda her new’ü numaralandır, sonda her hücreyi hangi pointer’ın gösterdiğini yaz. Kimse göstermiyorsa leak, gösteren var ama hücre silinmişse dangling.
Dangling pointer çözümleri
- Tombstone: her heap nesnesi için ek bir hücre; pointer’lar bu hücreyi gösterir. Nesne silinince tombstone nil olur. Bedel: zaman ve bellek.
- Locks-and-keys: pointer bir (key, adres) çifti; heap hücresi bir lock taşır. Erişimde key == lock kontrol edilir; silinen hücrenin lock’u değişir.
- En iyisi: programcıya silme yetkisi vermemek (Java, C#, LISP: garbage collection).
Heap yönetimi: garbage collection
| Yöntem | Nasıl | Artı | Eksi |
|---|---|---|---|
| Reference counting (eager) | Her hücre ona kaç pointer olduğunu sayar, 0 olunca serbest | Toplama anlık, duraklama yok | Sayaçlar için bellek ve zaman; döngüleri toplayamaz |
| Mark-sweep (lazy) | Bellek bitince: önce herkesi garbage say, kökten erişilenleri işaretle, işaretsizleri topla | Döngüleri toplar | Program toplama sırasında durur |
A.next = B; B.next = A; root = A; root = null;
reference count: A = 1 (B'den), B = 1 (A'dan) → hiçbiri 0 değil, toplanmaz
mark-sweep: kökten A'ya yol yok → ikisi de işaretsiz → toplanır
Type checking ve strong typing
Type checking, bir operatörün operandlarının uyumlu tiplerde olduğunun kontrolüdür. Uyumlu tip: ya doğrudan geçerli ya da dilin kurallarına göre otomatik dönüştürülebilir (coercion). Uygunsuz tipte operand kullanmak type error’dür.
Tip bağlamaları statikse kontrol neredeyse tamamen derleme zamanında yapılabilir; dinamik tip bağlamada (JavaScript, Python) kontrol çalışma zamanında yapılmak zorundadır. Hatayı ne kadar erken yakalarsan düzeltmesi o kadar ucuz.
Strongly typed dil: tip hataları her zaman yakalanır. ML ve F# strongly typed. Java ve C# neredeyse (açık cast hata üretebilir ama çalışmada yakalanır). C ve C++ değil: free union’lar ve pointer dönüşümleri kontrol dışı. Fazla coercion strong typing’in faydasını azaltır.
Type equivalence
İki tip ne zaman “aynı” sayılır?
| Name type equivalence | Structure type equivalence | |
|---|---|---|
| Kural | Aynı tip adıyla bildirildiyse | Tiplerin yapısı aynıysa |
| Uygulama | Kolay | Zor (iç içe yapılar, sıralama, dizi sınırları) |
| Esneklik | Kısıtlayıcı | Esnek |
| Güvenlik | Ayrı adlı tipler karışmaz | Ayrılmak istenen tipler karışabilir |
| Kullanan | Ada, Java sınıfları | C (struct ve union hariç) |
type Celsius = float;
type Fahrenheit = float;
c: Celsius; f: Fahrenheit;
f := c; name equivalence: HATA structure equivalence: geçerli
TUZAK · Ada anonim tipleri.
A, B : array (1..10) of Integer;satırında A ve B eşdeğer değil: anonim tip ifadesi her değişken için ayrı bir tip üretir. Eşdeğer olmaları için tipe önce bir ad verilmeli.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
| Dangling = kimsenin göstermediği hücre | O leak; dangling silinmiş hücreyi gösterir |
delete p sonrası sadece p tehlikeli | q gibi aynı adresi tutan her pointer dangling |
p + 1 adresi 1 byte artırır | sizeof(tip) kadar artar |
| C union’ı kontrol edilir | Free union, kontrol yok |
| Java’da union var | Yok |
| Reference counting her şeyi toplar | Döngüleri toplayamaz |
| Celsius = Fahrenheit her zaman | Name equivalence altında değil |
| Java strongly typed değil çünkü cast var | Cast hataları çalışmada yakalanır; neredeyse strongly typed |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed in Java?
class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } }
Point a = new Point(1, 2);
Point b = a;
b.x = 5;
System.out.println(a.x);Cevabı göster
Java'da sınıf tipli değişkenler reference'tır. b = a nesneyi kopyalamaz, aynı nesneye ikinci bir isim (alias) yaratır. b üzerinden yapılan değişiklik a'dan görünür.
What is printed in Java (16+)?
record P(int x, int y) {}
P p = new P(1, 2), q = new P(1, 2);
System.out.println(p.equals(q) + " " + (p == q));Cevabı göster
record, alanları değişmez (immutable) bir kayıt tipidir; derleyici equals'ı alan alan karşılaştıracak şekilde üretir. == ise iki ayrı nesnenin adresini karşılaştırır: false.
Consider the C++ code. After the last line, which pointers are dangling?
int *p = new int(5);
int *q = p;
delete p;Cevabı göster
delete p hücreyi serbest bırakır ama p'nin ve q'nun içindeki adres değişmez. İkisi de serbest bırakılmış bir hücreyi gösterir: dangling pointer. p'yi nullptr yapmak q'yu kurtarmaz; programcı q'nun varlığını unutmuş olabilir. Tehlike: o hücre başka bir değişkene verilirse q üzerinden onun verisi bozulur.
How many heap cells are lost (memory leak) after this C++ code?
int *p = new int;
p = new int;
int *q = p;
p = new int;Cevabı göster
1. hücre: p'nin ilk gösterdiği, ikinci new ile p başka yere yönelince hiçbir pointer onu göstermiyor → kayıp. 2. hücre: q hâlâ gösteriyor. 3. hücre: p gösteriyor. Toplam 1 lost heap-dynamic variable (garbage).
In C, int a[5] = {10, 20, 30, 40, 50}; int *p = a; p += 2; What are *p + 1 and *(p + 1)?
Cevabı göster
p += 2 pointer'ı iki eleman ileri taşır (adres 2 · sizeof(int) = 8 byte artar), p artık a[2]'yi gösterir. *p + 1: önce dereference (30), sonra +1 = 31. *(p + 1): bir sonraki eleman, a[3] = 40.
A pointer p to int holds address 1000 and int is 4 bytes. What address does p + 3 hold? What if p were a pointer to double (8 bytes)?
Cevabı göster
Pointer aritmetiği gösterilen tipin boyuna göre ölçeklenir: p + k = adres + k · sizeof(tip). int: 1000 + 12. double: 1000 + 24.
In C, union { int i; float f; } u; u.i = 1; and then u.f is read. What kind of union is this and what is the problem?
Cevabı göster
C ve C++ union'ları free union'dır: hangi alanın son yazıldığını tutan bir etiket (tag) yok, derleyici kontrol etmez. 1'in bitleri float olarak okunduğunda çok küçük bir sayı çıkar (Java'da Float.intBitsToFloat(1) = 1.4E-45). Ada ve ML'deki discriminated union bir tag taşır ve yanlış alan okumayı yakalar. Java ve C#'ta union yoktur.
Two types are declared as below. Under name type equivalence and under structure type equivalence, may a variable of type Celsius be assigned to a variable of type Fahrenheit?
type Celsius = float;
type Fahrenheit = float;Cevabı göster
Name type equivalence: tip adları aynıysa eşdeğer; Celsius ≠ Fahrenheit. Güvenli (sıcaklıklar karıştırılamaz) ama kısıtlayıcı. Structure type equivalence: yapı aynıysa eşdeğer; ikisi de float. Esnek ama programcının ayırmak istediği tipleri ayıramaz ve uygulaması zordur.
Which heap management technique fails to reclaim the two objects that point to each other below, after root is set to null?
A.next = B; B.next = A;
root = A;
root = null;Cevabı göster
Reference counting her hücrede ona kaç pointer olduğunu tutar, sayaç 0 olunca serbest bırakır. root gidince A'nın sayacı 1 (B'den), B'nin sayacı 1 (A'dan) kalır: hiçbiri sıfırlanmaz ama ikisine de programdan erişilemez. Mark-sweep kökten erişilebilen her şeyi işaretler, işaretsizleri toplar; döngüyü toplar.
Which dangling-pointer solution adds an extra heap cell between every pointer and its target, and sets it to nil when the target is deallocated?
Cevabı göster
Tombstone yönteminde pointer'lar doğrudan nesneyi değil bir mezar taşı hücresini gösterir. Nesne silinince mezar taşı nil olur, kimse silinmiş belleğe erişemez. Bedeli: fazladan bellek ve her erişimde ek bir dolaylılık. Locks-and-keys: pointer bir (key, adres) çifti, hücre bir lock taşır; erişimde key ile lock karşılaştırılır.
Which of these languages are strongly typed or nearly so: C, C++, Java, ML? Give one reason why C is not.
Cevabı göster
Strongly typed: tip hataları her zaman (derlemede ya da çalışmada) yakalanır. ML ve F# strongly typed. Java ve C# neredeyse (açık cast'ler hata üretebilir ama çalışmada yakalanır). C ve C++ değil: free union'lar ve kontrolsüz pointer dönüşümleri tip hatasını kaçırır. Coercion kuralları da strong typing'i zayıflatır.
In Ada, A, B : array (1..10) of Integer; declares two variables with an anonymous array type. Are A and B type equivalent?
Cevabı göster
Ada'da anonim bir tip ifadesi her değişken için ayrı, benzersiz bir tip yaratır; bu yüzden A ve B eşdeğer sayılmaz (A := B derlenmez). Eşdeğer olmaları için önce isimli bir tip tanımlanır: type List is array (1..10) of Integer; A, B : List;. Ada name equivalence'ı ciddiye alır.
Doğrulama: Java çıktıları JDK ile çalıştırıldı (java verify/SEN2001/W05b.java); C union'ının bit yeniden yorumlaması Java'nın Float.intBitsToFloat fonksiyonuyla aynı bitler üzerinden gösterildi. C pointer aritmetiği, new/delete sonrası kayıp ve dangling sayıları ile reference counting döngüsü node'da küçük bir bellek benzetimiyle hesaplandı (verify/SEN2001/05b.mjs). C derleyicisi kurulu olmadığı için C kodları doğrudan çalıştırılmadı.