1 puan yazan GN⁺ 19 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • C dilinin kuralları, pointer karşılaştırması, aliasing, null pointer ve başlatılmamış değerler gibi basit görünen kodları bile tanımsız davranışa dönüştürebilir
  • Tamsayı sabitleri ile sizeof, karakter sabitleri ve uint8_t aritmetiği, tip seçimi ve integer promotion nedeniyle platforma, gösterime ve ara atamanın yapıldığı yere göre farklı sonuçlar verebilir
  • Fonksiyon bildirimlerindeki foo() ve foo(void), prototip eksikliği, varsayılan argüman yükseltmeleri ve dönüş değeri olmayan fonksiyonlar, C ve C++ arasında yasallık veya davranış açısından farklılık gösterir
  • Diziler pointer değildir; dizi parametreleri pointer’a dönüştürülür ve a, &a, &a[0] aynı adresi taşısa bile tipleri farklı olduğu için birbirinin yerine kullanılamaz
  • Operatör önceliği ile değerlendirme sırası ayrı şeylerdir; switch gövdesinin yapısından geçici nesnelerin ömrüne kadar standart metni gerçek çalışma sonucunu belirler

Tanımsız davranış ve pointer kuralları

  • Pointer karşılaştırması ve strict aliasing kuralı

    • Aynı tipteki p ve q pointer’ları aynı adresi gösterse bile, farklı nesnelerden türemişlerse ve aynı aggregate veya union nesnesinin parçası değillerse p == q karşılaştırması tanımsız davranış olabilir
    • Pointer’ların yalnızca sayısal adreslerden daha soyut olduğu konusu ilgili yazıda devam ediyor
    • Bir int nesnesine short lvalue ile erişmek, strict aliasing kuralı gereği tanımsız davranıştır
    • unsigned char pointer’ı istisna olarak her türlü nesneye alias yapabildiğinden, bir int nesnesine unsigned char lvalue ile erişmek yasaldır
    • unsigned char için padding bit ve trap representation bulunmadığı garanti edilir; C11’den itibaren signed char için de padding bit olmadığı garanti edilir
    • Tipe dayalı alias analizi ilgili yazıda ele alınıyor
  • Null pointer ve pointer gösterimi

    • Bir null pointer’ın bit gösterimi mutlaka tüm bitleri 0 olmak zorunda değildir
    • C standardı null pointer constant kavramını tanımlar, ancak çalışma zamanındaki null pointer gösterimini veya genel pointer gösterimini tanımlamaz
    • Symbolics Lisp Machine 3600, sayısal pointer yerine <array-object, index> biçiminde bir tuple kullanır ve null pointer gösterimi <nil, 0> şeklindedir
    • Ek örnekler clc SSS 5.17 içinde bulunabilir
    • Sabit 0, bağlama göre tamsayı veya null pointer olabilir; (void *)0 ise null pointer olarak değerlendirilir
    • e ifadesinin 0 değerini vermesi, (void *)e ifadesinin null pointer olacağını garanti etmez
    • Yalnızca null pointer constant bir pointer türüne dönüştürüldüğünde null pointer’a eşit olması garanti edilir
    • Null pointer üzerinde aritmetik yapmak tanımsız davranıştır; bu nedenle e bir null pointer olsa bile e + 0 ifadesinin null pointer olacağı garanti edilmez
  • Başlatılmamış değerler

    • Otomatik saklama süreli başlatılmamış bir nesne okunduğunda, bu nesne register saklama sınıfında olabiliyorsa ve adresi hiç alınmamışsa C11 § 6.3.2.1 ¶ 2 uyarınca bu tanımsız davranış olur
    • Bu kural, DR338 içinde ele alınan Intel Itanium mimarisiyle bağlantılıdır
    • Itanium’un genel amaçlı tamsayı register’ları 64 bit ve bir trap bit taşır; bu trap bit, register’ın başlatılıp başlatılmadığını gösteren NaT (not-a-thing) değeridir
    • Değişkenin adresi alınırsa bu koşul ortadan kalkar, ancak değer yine de indeterminate olur ve trap representation veya unspecified value olabilir
    • Bir trap representation okumak, C11 § 6.2.6.1 ¶ 5 uyarınca tanımsız davranıştır
    • Eğer unspecified value söz konusuysa x != x sonucunun true veya false olması mümkündür; int x unspecified ise x *= 0 sonrasında bile x’in 0 olduğu garanti edilmez
    • Indeterminate ve unspecified value, DR260, DR451, N1793, N1818, N2012, N2013, N2221 belgelerinde tartışılmaktadır
  • unsigned char ve memcpy

    • unsigned char türü, C11 § 6.2.6.1 ¶ 3 uyarınca trap representation içermez; bu yüzden başlangıç değeri unspecified olur
    • StackOverflow’da C komitesi üyesinin yanıtı, standart kütüphane fonksiyonu memcpy çağrısından sonra x değerinin specified hale gelmesi gerektiğini ve bu yoruma göre x != x sonucunun false olacağını savunur
    • C standardında bunu açıkça destekleyen dayanak net değildir ve DR451 içindeki komite yanıtı, indeterminate value üzerinde kütüphane fonksiyonu kullanmanın tanımsız davranış olduğunu söyleyerek bu yorumla çelişir
    • Bu soru hâlâ açık durumdadır; ek tartışma için Uninitialized Reads yazısına bakılabilir

Tamsayı sabitleri, terfi ve sizeof

  • Tamsayı sabitlerinin gösterimi ve türü

    • Soneki olmayan ondalık tamsayı sabitleri her zaman signed tür listesinden seçilir; ancak sekizlik ve onaltılık sabitler signed veya unsigned tür olabilir
    • C17 § 6.4.4.1'e göre bir tamsayı sabitinin türü, ilgili değeri ifade edebilen listedeki ilk tür olarak belirlenir
    • Sonek olmadığında ondalık sabitler için sıra int, long int, long long int; sekizlik ve onaltılık sabitler için ise int, unsigned int, long int, unsigned long int, long long int, unsigned long long int şeklindedir
    • INT_MAX+1 ile UINT_MAX arasındaki sabitlerin türü, ondalık mı yoksa onaltılık mı olduklarına göre değişebilir ve değişken argümanlı fonksiyon çağrıları gibi ABI'ye duyarlı kodlarda fark yaratabilir
    • Arm 32-bit architecture ABI içinde int ve long 32 bit olarak tek bir register ile aktarılırken, long long 64 bit olarak iki register ile aktarılır
    • int'in 32 bit olduğu platformlarda -1 < 0x8000 ifadesi true olurken, int'in 16 bit olduğu platformlarda false olur; bu da taşınabilirlik sorunu yaratabilir
    • generic selection, C++ overload fonksiyonları ve sizeof(0x80000000) == sizeof(2147483648) gibi ifadelerde de sabit türü farkı sonucu değiştirebilir
  • sizeof(int) > -1

    • sizeof işleci, size_t türünde unsigned bir tamsayı döndürür
    • C11 § 6.3.1.8'deki usual arithmetic conversions uyarınca, signed operand unsigned operanddan daha düşük rank'e sahipse aynı rank'teki unsigned türe dönüştürülür
    • -1'e karşılık gelen signed integer, unsigned türe dönüştürüldüğünde o rank'teki en büyük unsigned integer olur
    • Bu nedenle sizeof(int) > -1 her zaman false olarak değerlendirilir
  • Karakter sabitlerinin türü

    • C'de karakter sabitleri, C11 § 6.4.4.4 ¶ 10'a göre int türündedir
    • Bu nedenle sizeof(char) == sizeof('x') ifadesinin her zaman true olacağı garanti edilmez; yalnızca sizeof(int) == sizeof('x') garanti edilir
    • integer character constant, bir veya daha fazla multibyte character dizisi olabilir; dolayısıyla 'abc' de geçerlidir ve bunun gösterimi implementation-defined'dır
    • Tek bir karakter içeren integer character constant'ın değeri, aynı tek karakteri gösteren char türündeki nesnenin tamsayı gösterimiyle aynıdır
  • uint8_t aritmetiği ve bölme

    • a, b, c okunmadan önce başlatılmış olsa bile, tamsayı terfileri ve ara atamanın konumu nedeniyle x ve z değerleri farklı olabilir
    • Her değişken değeri int boyutuna terfi ettirildikten sonra toplama ve bölme yapılır; her atama sonucu ise ilgili değişken türüne truncate edilerek saklanır
    • Örneğin a=255, b=1, c=2 ise x, ((255 + 1) / 2) % 256 = 128 olur
    • Ara değişken y, (255 + 1) % 256 = 0 olur; ardından z, (0 / 2) % 256 = 0 olur ve dolayısıyla 128 != 0 olur
    • unsigned integer overflow tanımlı davranıştır
    • Modüler aritmetik, toplama üzerine dağıldığından bölmeyi toplama ile değiştirirseniz x ve z her zaman aynı olur
    • İlk atamayı uint8_t x = ((uint8_t)(a + b)) / c; olarak değiştirseniz de x ve z her zaman aynı olur
  • const değişkenler ve variable length array

    • const ile nitelenmiş n ve m değişkenleri dizi boyutu olarak kullanılsa bile, bunlar C'de integer constant expression değildir
    • C11 § 6.6 ¶ 6'da integer constant expression; integer constant, enumeration constant, character constant, sonucu integer constant olan sizeof, _Alignof, cast'in doğrudan operandı olan floating constant gibi yapılarla sınırlıdır
    • Dizi boyutu ifadesi integer constant expression değilse, C11 § 6.7.6.2 ¶ 4 uyarınca variable length array olur
    • variable length array file scope'ta izin verilmediği için, genel dizi x içeren compilation unit derlenmez
    • block scope'ta variable length array'e izin verildiğinden, yerel dizi y içeren compilation unit derlenebilir
    • variable length array, implementasyonun desteklemek zorunda olmadığı bir conditional feature olduğundan, bunu desteklemeyen derleyicilerde block scope örneği de derlenmeyebilir
    • C++'ta ise her iki compilation unit de derlenir ve C++'ta variable length array kavramı olmadığından y, 42 elemanlı normal bir dizi olarak derlenir

Fonksiyon bildirimleri, dönüş değerleri, linkage

  • foo() ve foo(void)

    • foo() biçimindeki bir fonksiyon bildirimi, argüman sayısı ve türleri bilinmeyen bir fonksiyonu bildirir; foo(void) ise argümanı olmayan bir nullary function bildirir
    • Bu fark, fonksiyon bildirimi·tanımı·prototipiyle ilgili yazıda ele alınıyor
    • Argüman listesi olmayan bildirim, yalnızca fonksiyon adını tanıttığı ve argüman sayısı ile türlerini tanımlamadığı için, sonraki fonksiyon tanımıyla birleşerek yasal olabilir
    • Prototip olmadan fonksiyon çağrılırsa default argument promotions uygulanır ve float, double'a yükseltilir
    • Yükseltme sonrası fonksiyon türü, gerçek fonksiyon tanımının türüyle uyumlu değilse bildirim ile tanımın birleşimi geçerli değildir
    • Bildirimsiz fonksiyon çağrısı C'de örtük fonksiyon bildirimi sayesinde derlenebilir, ancak C++'ta derleme hatasıdır
    • Bildirim olmadan bar(42) gibi bir çağrı yapılırsa tamsayı argüman yükseltmesi uygulanır ve 42, int olarak ifade edilir; bu nedenle bar, herhangi bir dönüş türü T için T (*)(int) ile uyumlu değilse bu tanımsız davranış olur
  • Değer döndürmeyen value-returning fonksiyon

    • Dönüş türü int olan bir fonksiyon değer döndürmese bile, C'de çağrı sonucunun değeri kullanılmadığı sürece yasal olabilir
    • K&R C'de void türü yoktu ve tür belirtilmezse varsayılan tür olarak int kabul ediliyordu; bu yüzden değer döndürmeyen fonksiyonlar ile örtük int kuralı tarihsel olarak bağlantılıdır
    • Örtük int kuralı C99'da kaldırıldı; ilgili tartışmalar N661 ve C99 rationale içinde yer alıyor
    • C17 § 6.9.1 ¶ 12, fonksiyon sonundaki } karakterine ulaşıldığında ve çağıran taraf fonksiyon çağrısının değerini kullandığında bunun tanımsız davranış olduğunu belirtir
    • C++98 § 6.6.3 ¶ 2'de, value-returning bir fonksiyonun sonuna kadar akıp gitmek başlı başına değersiz bir return ile eşdeğerdir ve value-returning fonksiyonda bu, tanımsız davranış olur
    • C++ derleyicileri, abort_program()'ın hangi dalda sonlandığını genel olarak kanıtlayamadığı için bu tür durumlarda hata yerine yalnızca tanı koyabilir
  • linkage ve extern

    • Önceki bildirimin görünür olduğu bir kapsamda aynı tanımlayıcı extern ile yeniden bildirilirse, sonraki bildirimin linkage'ı önceki bildirimin linkage'ı ile aynı olur
    • C17 § 6.2.2 ¶ 4, önceki bildirim internal veya external linkage belirttiyse sonraki extern bildiriminin de aynı linkage'a sahip olduğunu söyler
    • Önceki bildirim görünür değilse veya önceki bildirimde linkage yoksa extern tanımlayıcı external linkage'a sahip olur
    • Ters sıradaki bildirim birleşimleri tanımsız davranış olabilir ve GCC ile Clang bunu yakalar

Niteleyiciler ve tamamlanmamış türler

  • Fonksiyon parametrelerinde const

    • Fonksiyon bildiriminde parametre x, const ile nitelenmiş olup fonksiyon tanımında böyle değilse ve fonksiyon gövdesinde x'e değer yazılsa bile bu yasaldır
    • C11 § 6.7.6.3 ¶ 15'e göre, fonksiyon parametresi türü uyumluluğu ve composite type değerlendirilirken, qualified type olarak bildirilen her parametre unqualified version olarak ele alınır
    • Aynı konu DR040 içinde de ele alınmıştır
  • Fonksiyon dönüş türünde const

    • Yalnızca fonksiyon tanımının dönüş türü const ile nitelenmiş, bildirimin dönüş türü nitelenmemişse bunun yanıtını basitçe doğru ya da yanlış diye vermek zordur
    • Genel uzlaşı, rvalue üzerindeki niteleyicilerin yok sayılması gerektiği yönündedir; ancak C11'e kadar standart metni bunu açıkça ele almıyordu
    • C17'de cast, lvalue conversion ve function declarator bağlamlarında rvalue niteleyicilerinin yok sayılması gerektiği netleşti
    • C17 § 6.7.6.3 ¶ 5'te, fonksiyonun döndürdüğü türün T'nin unqualified version olduğu açıkça belirtilir; bu ifade C17'de eklenmiştir
    • Dönüş türündeki const niteliği farklı olsa da fonksiyon türü ataması yasal olabilir
    • Ek tartışmalar DR423 ve DR481 içinde bulunabilir
  • Tamamlanmamış struct ve global değişken

    • Global değişken bildirimi sırasında struct foo tamamlanmamış bir tür olup boyutu bilinmese bile, daha sonra aynı translation unit içinde tür tamamlanıyorsa belirli durumlarda buna izin verilir
    • Benzer mantık global değişkenlere veya tamamlanmamış tür dizilerine de uygulanır
    • Bu konu DR016 içinde de ele alınır
  • void türünde external object

    • Internal linkage'a sahip void türünde değişken bildirimi yasal değildir; ancak external linkage'a sahip void türünde değişken bildirimi sözdizimsel olarak yasaldır ve C11 standardında da açıkça yasaklanmaz
    • C11 § 6.2.5 ¶ 19'a göre void türü, değerlerin boş kümesinden oluşan tamamlanamayan eksik nesne türüdür
    • C11 § 6.3.2.1 ¶ 1, lvalue'yu void dışındaki bir nesne türünün ifadesi olarak tanımladığı için, void türündeki foo nesnesinin adı geçerli bir lvalue değildir
    • C11 açısından external void nesnesi üzerinde anlamlı ve conforming bir işlem düşünmek zordur
    • DR012, tür const void yapıldığında foo nesnesinin adresini almanın yasal olduğunu ele alır; bu, amaçlanmış bir özellikten çok bir oversight gibi görünür
  • İşaretçi-const dönüşümü

Diziler, string literal'ler, işaretçi ayarlaması

  • Dizi işaretçi değildir

    • Dizi başlatma ile işaretçi başlatma eşdeğer değildir
    • İlk biçim, otomatik veya statik saklama süresine sahip değiştirilebilir bir diziyi başlatır
    • İkinci biçim, statik saklama süresine sahip bir diziyi gösteren bir işaretçiyi başlatır ve bu dizi mutlaka değiştirilebilir olmak zorunda değildir
    • Dizi, işaretçi değildir; ayrıntılar ilgili yazıda ele alınıyor
  • a, &a, &a[0]

    • int a[42]; için a, &a ve &a[0] ifadelerinin tümü dizinin ilk elemanının adresi olarak değerlendirilir
    • Ancak bu üç ifadenin türleri birbirinden farklıdır, bu yüzden birbirlerinin yerine kullanılamazlar
    • Ayrıntılar ilgili yazıda ele alınıyor
  • Dizi parametreleri ve yerel diziler

    • Fonksiyon parametresi türü “T dizisi” ise bu, “T gösteren işaretçi”ye ayarlanır
    • Parametre x, int[42] gibi görünse de gerçekte int * olarak ele alınır
    • Yerel değişken y, int[42] ise sizeof(y) değeri 42 * sizeof(int) olur
    • Genel olarak nesne işaretçisinin boyutu 42 tamsayının boyutuna eşit olmadığından sizeof(x) == sizeof(y) çoğu durumda false olur
    • Ayrıntılar ilgili yazıda ele alınıyor

Operatörler, değerlendirme sırası ve kontrol akışı

  • x+++y

    • C'de C++'taki gibi yeni operatörler tanımlanamadığı için +++ gibi yeni bir operatör yoktur
    • x+++y, mevcut operatörlerin birleşimi olarak yorumlanır ve (x++) + y ile eşdeğerdir
    • --*--p de yeni bir operatör değil, mevcut operatörlerin birleşimidir
    • --*--p, --(*(--p)) ile eşdeğerdir ve örnekte -1 olarak değerlendirilir; yan etki olarak x[0] içine -1 atanır
  • Aritmetik operandların değerlendirme sırası

    • Operatör önceliği iyi tanımlanmıştır, ancak aritmetik operandların değerlendirme sırası tanımlı değildir
    • (x=1) + (x=2) ifadesinde iki atamanın sırası tanımlı olmadığı için x'in son değerinin 1 mi 2 mi olacağı belirli değildir; bu nedenle tanımsız davranıştır
    • -std=c11 -O2 seçeneğinde GCC 8.2.1 örnek ifadeyi 4, Clang 7.0.0 ise 3 olarak değerlendirir
  • Mantıksal operatörlerin değerlendirme sırası

    • Mantıksal operatörler && ve || için operandların değerlendirme sırası da iyi tanımlanmıştır
    • C standardının ifadesiyle, ilk operandın değerlendirilmesi ile ikinci operandın değerlendirilmesi arasında bir sequence point bulunur
    • Örnekte önce x=1 değerlendirilip true olur, ardından x=2 değerlendirilip yine true olur; dolayısıyla ifadenin tamamı true olur
  • switch için serbest gövde yapısı

    • switch ifadesinin gövdesi herhangi bir statement olabilir; bu yüzden loop ve if içeren karışık yapılar da geçerli olabilir
    • Kontrol ifadesi her zaman false olan bir if ifadesinin içindeki true branch bile, bir case label içeriyorsa ilgili statement canlı hale gelir ve printf("1"); dead code değildir
    • case 2ye atlandığında loop'un clause-1'i ve kontrol ifadesi çalışmayabilir; bu yüzden i değişkeni önceden başlatılmış olmalıdır
    • case 1 içinde break olmadığı için fall through gerçekleşse bile, case 1 ifin true branch'inde ve case 2 false branch'inde ise case 2 atlanıp case 3 ile devam edilebilir
    • Üç çağrı foo(0); foo(1); foo(2); sonrasında konsol çıktısı 02313223 olur
    • Loop ve switch'in birleştirildiği ünlü gerçek örneklerden biri Duff's device'tır

Geçici nesne ömrü ve C standart sürümleri arasındaki farklar

  • Belirli bir kod parçası C11'de tanımsız davranış olabilirken C99'da böyle olmayabilir
  • C11'de belirli nesnelerin ömrü kısalmıştır; bir fonksiyon çağrısının döndürdüğü nesne yalnızca sağ tarafın değerlendirildiği süre boyunca yaşar
  • C99'da aynı nesne enclosing block'un sonuna kadar yaşar
  • Ömrü sona ermiş bir nesneye başvurmak, C11 § 6.2.4 ¶ 2 uyarınca tanımsız davranıştır
  • C99'da da automatic storage duration'a sahip nesnelerin ömrü en yakın enclosing block'a bağlıdır; bu nedenle ilgili block dışından nesneye başvurmak tanımsız davranıştır
  • C11 § 6.2.4 ¶ 8, struct veya union türünde bir non-lvalue expression array member içeriyorsa, automatic storage duration ve temporary lifetime'a sahip bir nesneye başvurulduğunu belirtir
  • Bu geçici nesnenin ömrü ifade değerlendirilirken başlar ve onu içeren full expression veya full declarator değerlendirmesi bittiğinde sona erer
  • Temporary lifetime'a sahip bir nesneyi değiştirmeye yönelik girişim tanımsız davranıştır
  • İlgili örnek N1285 belgesinden alınmıştır; ek tartışmalar da orada yer alır

1 yorum

 
Lobste.rs görüşleri
  • 4. soru C23'te geçerli değil ama öncesinde geçerliydi
    10. soru ne doğru ne yanlış cevaba sahip, bu yüzden çoktan seçmeli demek için biraz rahatsız edici
    15. soru, özellikle 13. soruyla bağlantılı olarak teknik olarak yanlıştı ve 20. soru da “belirtilmemiş” olduğundan yine hiçbir cevap doğru değildi
    30. soru ise okunuşuna göre muğlak
    Yine de 31 sorunun 27'sini bildim; derleyici geliştiricisi olmamın da biraz faydası oldu

  • Yaklaşık dört soru çözdükten sonra, C'nin basit olduğu ve yan projelerde kullanılmaya değer olduğu yönündeki içimde kalmış his kayboldu

    • GCC veya clang'de -std=<language-standard> -pedantic -Wall -Wextra kullanıp her uyarı çıktığında gerçekten düzeltirseniz ve pointer cast ile pointer manipülasyonundan mümkün olduğunca kaçınırsanız, büyük tuzaklardan uzak durmak mümkün gibi görünüyor
      Günümüzde GCC/clang uyarıları oldukça iyi; <language-standard> olarak c89, c99, c11, c23 kullanılabiliyor
    • C basit ama tanımsız davranış etrafındaki cambazlık hiç de basit değil
      tcc gibi garip optimizasyonlar yapmayan bir derleyici kullanırsanız daha az tuhaf sürpriz yaşarsınız
  • Sadece “buradaki en saçma davranış ne olabilir?” ölçütüyle seçip 32 sorunun 21'ini bildim
    Yanlışlarımın çoğu, o saçmalığın seviyesini yeterince derin düşünmememden kaynaklanıyordu
    C'ye en son 15 yıldan daha uzun zaman önce biraz dokunmuştum ama böyle bir test görünce yeniden denemek istemedim

    • Bu arada ChatGPT, her cevabın ardından gelen ek açıklamalara bakmadan 32 sorunun 22'sini doğru bildi
  • C23'e göre 4. sorunun cevabı geçerli değil

  • İlginç şekilde, bir süredir C kullanmıyor olmama rağmen 32 sorunun 27'sini bildim
    Zaten bu yüzden statik denetleyicilere ve linter'lara güveniyordum

  • 1. sorundan itibaren içime sinmedi
    O pointer'ların nereden gelebileceği hesaba katılmamıştı ve orada anlatılan durumun oluşması için çok özel koşullar gerekiyor
    Çoğu durumda, o pointer'ları oluşturmaya çalışmak zaten tanımsız davranış ama yine de adil sayılabilir
    3. soru gerçekten şaşırtıcıydı; C'nin bir başka tuzağıydı
    C tamsayı literal'lerinin baştan belirlenmiş bir tipe sahip olması başlı başına çok sinir bozucu
    Tamsayı yükseltme kuralları bunu bir ölçüde dengeliyor ama aynı zamanda hata kaynağı da oluyor
    Modern dillerin çoğu, hatta belki hepsi, örtük sayısal cast işlemlerini yasaklamalı; mümkünse literal tipini bağlamdan çıkarmalı, bu mümkün değilse açık cast istemeli
    6. sorudan sonra teste güvenemediğim için bıraktım
    İlk başta bunun nedeni 5. sorunun cevabının fiilen 6. soruyu yanlış yaptıracak şekilde tasarlanmış olmasıydı ama tekrar bakınca 6. sorunun kendisi de yanlış görünüyor
    Açıklama, fonksiyon çağrısının tanımsız davranış olduğunu söylüyor ama soru fonksiyon tanımının yasal olup olmadığını soruyordu ve büyük ihtimalle yasaldı

    • Bellekte iki dizi bitişikse ve biri ilk elemana, diğeri ise ötekinin son elemanından hemen sonraki konuma işaret ediyorsa böyle bir durum oluşur
      Ve bu da çok nadir görülen bir şey gibi durmuyor
  • switch() sorusu gerçekten çok iyiydi
    Zorluydu ama kafada çözme süreci çok keyifliydi