4 puan yazan GN⁺ 2023-10-10 | 1 yorum | WhatsApp'ta paylaş
  • 2023’e girerken C kodu yazma biçimi önemli ölçüde değişti ve stil; kısa tür adları, null ile sonlandırılmış dizgilerden kaçınma, struct döndürme ve tek çeviri birimiyle derleme etrafında yeniden şekillendi
  • u8, i32, size, s8 gibi kısa takma adlar ile const ve struct kullanımını kaldırmak, tekrarlanan bildirimlerdeki görsel gürültüyü ve bilişsel yükü azaltmaya yönelik bir tercih
  • Dizgiler null ile sonlandırma yerine data ve len içeren s8 fat pointer ile ele alınıyor; Win32 ve UTF-16 ortamları için c16 ve s16 birlikte kullanılıyor
  • Fonksiyon tasarımında out parametreleri yerine struct döndürme tercih ediliyor; sıfırla başlatılmış dönüş değerinde yalnızca başarı anında ok ayarlanan bir kalıp kullanılıyor
  • Makrolar, assert, Win32 bildirimleri ve inline assembly dahil okunabilir yerel kurallara önem veriliyor; ancak başka projelere katkı yapılırken o projenin stili izleniyor

Kısa tür adlarıyla niyeti ortaya koymak

  • Temel tamsayı, karakter ve işaretçi boyutu türleri için kısa takma adlar kullanılıyor
    • Örnek: u8, c16, b32, i32, u32, u64, f32, f64, uptr, byte, size, usize
  • Bu adlar programın tamamında sık geçtiği için kısalık, okuma ve inceleme açısından doğrudan fayda sağlıyor
  • _t soneki artık kullanılmıyor; bugün görsel olarak dikkat dağıtan bir unsur gibi geliyor
  • signed tür öneki olarak s yerine i tercih ediliyor
    • s, dizgi tür adları için ayrılmış tutuluyor
  • Boyut türü için isize yerine size kullanılıyor
    • Çünkü signed size daha önemli varsayılan olarak görülüyor
    • usize, daha çok dış arayüzlerle etkileşimde kullanılan dar kapsamlı bir tür
  • b32, “32 bit boolean” niyetini ortaya koyuyor
    • _Bool yerine doğal word boyutu kullanılıyor
    • Pratikte çoğu zaman register’da ya da struct padding’inde yer aldığı düşünülüyor
    • Bellek gerçekten önemli olduğunda boolean değerler bir flags değişkenine sıkıştırılıyor
  • c16, Win32’de gerekli olan UTF-16 karakterleri için bir tür
    • char16_t tabanlı olması, GDB gibi debugger’ların karakter verisi olarak göstermesine yardımcı oluyor
    • Win32’nin resmi tür adı wchar_t, ancak UTF-16’yı açıkça belirten ad tercih ediliyor
  • u8, octet ve çoğunlukla UTF-8 verisi için; byte ise ham bellek ve özel aliasing türü olarak ayrılıyor
  • Sabit genişlikli türleri desteklemeyen sistemler için kaygılanmak pratik bulunmuyor
    • int_fast32_t gibi uzun tür adları da gereksiz israf olarak görülüyor
  • Kod parçaları tek başına gösterilirken bu takma adlar tek başına kullanılmıyor
    • Okurun bağlamı anlaması için typedeflerin de birlikte verilmesi gerekiyor

Makro ve assert kuralları

  • Fonksiyon benzeri makrolarda küçük harf kullanılıyor
    • Örnek: countof(a), lengthof(s), new(a, t, n)
  • Sabitler için hâlâ ALL_CAPS tercih ediliyor, ancak fonksiyon benzeri makrolarda küçük harf daha okunur görülüyor
  • Fonksiyon benzeri makrolarda namespace sorunu sıradan makrolara göre daha az
    • new() makrosu ile new değişkenleri ve alanları aynı anda bulunabiliyor
    • Çünkü fonksiyon çağrısı biçiminde değilse makro olarak genişletilmiyor
  • GCC ve Clang için assert makrosu while (!(c)) __builtin_unreachable() biçimini kullanıyor
  • Bu assert yaklaşımı, build ayarlarını ayrıca ayırmayı gerektirmiyor
    • Debug ve release build’leri için ayrı tanımlar gerekmiyor
    • Davranıp davranmaması Undefined Behavior Sanitizer, yani UBSan varlığıyla kontrol ediliyor
    • libubsan, dosya adı ve satır numarası içeren tanı çıktısı sağlıyor
    • Release build’de pratik bir optimizasyon ipucu haline geliyor
  • Release build’de assertion’ları etkinleştirmek için -fsanitize-trap ile UBSan trap moduna alınıp en azından -fsanitize=unreachable açılıyor
  • -funreachable-traps ile de teorik olarak mümkün, ancak yazıldığı tarih itibarıyla son birkaç GCC sürümünde bozuk

Bildirimlerde azaltılanlar

  • Parametrelerde const kullanılmıyor
    • Optimizasyonda pratik bir rolü olmadığı düşünülüyor
    • Hata yakaladığı ya da yakalayacağı bir örnek hatırlanamadığı söyleniyor
    • İyi parametre adlarının prototipi belgeleme rolü için yeterli olduğu düşünülüyor
  • constun kaldırılması, bilişsel yükü ve görsel gürültüyü azaltarak üretkenliği artıran bir değişiklik
  • Küçük bir istisna olarak, statik tabloları kodun yakınındaki salt okunur belleğe koymak için ipucu olarak const hâlâ tercih ediliyor
    • Gerekirse const cast ile kaldırılıyor
  • Null işaretçi için literal 0 kullanılıyor
    • Yaklaşık 7 yıldır kullanılan bir stil
    • Teorik kusur olasılığı olsa da yüz binlerce satır kodda gerçek bir örnek görülmemiş
  • restrict yalnızca gerektiğinde kullanılıyor
    • Out parametreleri döngülerde kullanılmayacak şekilde ya da out parametrelerinin kendisinden kaçınacak biçimde kod kurgulanıyor
  • inline kullanılmıyor
    • Çünkü her şey tek bir çeviri birimi olarak derleniyor
  • Tüm struct’lar için typedef yapılıyor
    • struct anahtar sözcüğünü kaldırmak kodu daha okunur hale getiriyor
    • Özyinelemeli struct’lar için hemen üstte forward declaration bulunuyor; alanlarda kısa adlar kullanılıyor
  • Entry point dışındaki tüm fonksiyonlar static olarak bildiriliyor
    • Çünkü tek çeviri birimiyle derleme varsayılıyor
  • Kısa tür adları, constun kaldırılması ve structun kaldırılması sayesinde fonksiyon dönüş türü ile fonksiyon adını aynı satıra rahatça koymak mümkün oluyor
  • Tür adlarını büyük harfle yazılan bir dönem de olmuş, ancak sonunda bundan vazgeçilmiş

Dizgiler null ile sonlandırma yerine s8

  • En üretken değişikliklerden biri, null ile sonlandırılmış dizgileri tamamen reddedip data ve len içeren s8 dizgi türünü kullanmaya başlamak
  • s8 yapısı:
    • u8 *data
    • size len
  • s8(s) makrosu, C dizgi literallerini s8 dizgisine sarıyor
  • s8, fat pointer gibi değer olarak geçiriliyor ve değer olarak döndürülüyor
  • s8, fonksiyon öneki olarak da kullanışlı
    • Çünkü str ailesindeki adlar ayrılmış durumda
    • Örnek: s8span, s8equals, s8compare, s8hash, s8trim, s8clone
  • Literal karşılaştırması için s8equals(tagname, s8("body")) gibi bir biçim kullanılıyor
  • Flexible array member ile boyutu ve diziyi tek allocation’da birleştirme yöntemi de denenmiş, ancak esneklik eksikliğinin faydadan ağır bastığı düşünülüyor
  • Basit programlarda dizgi türüne gerek olmadığı düşünülen durumlar da olmuş, ancak genellikle bunun yanlış bir değerlendirme olduğu düşünülüyor
  • UTF-16 desteği için s16 türü de kullanılıyor
    • c16 *data ve size len içeriyor
    • Makroda literale u ekleme yönteminden henüz tam emin olunmamış

Struct döndürme ve başlatma biçimi

  • Out parametreleri yerine struct döndürme tercih ediliyor
    • Aslında birden çok değer döndürme yöntemi, ancak destructuring yok
  • Örnek i32parse(s8), ayrıştırma sonucu olan value ile durum bilgisi olan ok değerini birlikte döndürüyor
  • Ek kopyalama maliyetinin pratikte büyük bir sorun olmadığı düşünülüyor
    • Çağrı kuralı bunu gizli bir restrict out parametresine dönüştürebiliyor
    • Inline edilirse dönüş değeri overhead’i anlamını yitiriyor
  • Bu yöntem, hatayı özel bir null dönüş gibi in-band sinyallerle göstermeye yönelik ayartmayı azaltıyor
  • Fonksiyonun başında sıfırla başlatılmış bir dönüş değeri oluşturup tüm returnlerde onu kullanma kalıbı tercih ediliyor
    • Hata durumunda hemen sıfırla başlatılmış halde döndürülüyor
    • Başarı yolunda, dönüşten hemen önce ok true olarak ayarlanıyor
  • Statik veri ve s8/s16 makroları dışında initializer kullanımı da azaltılıyor
    • Designated initializer’dan da kaçınılıyor; atama ifadeleriyle başlatma yapılıyor
  • Atama ifadeleriyle başlatma okunabilir; her atama arasında sequence point bulunduğu için açık sıralama sağlıyor
  • Rastgele sayı üretim fonksiyonları gibi çağrı sırasının sonucu etkileyebileceği başlatmalarda olası değer durumlarını düşünmek gerekmiyor

Win32 bildirimleri ve inline assembly

  • __attribute__ yerine __attribute tercih ediliyor
    • Sondaki __ soneki aşırı ve gereksiz görülüyor
  • Win32 sistem programlamasında windows.h dahil edilmiyor; gereken prototipler elle yazılıyor
    • Çünkü genellikle gereken bildirim ve tanım sayısı çok fazla olmuyor
    • Build süresini azaltıyor ve namespace’i daha az kirletiyor
    • DWORD, BOOL, ULONG_PTR yerine u32, b32, uptr gibi özel türlerle daha temiz uyum sağlıyor
  • Win32 bildirim örneklerinde W32(r) __declspec(dllimport) r __stdcall makrosu kullanılıyor
    • ExitProcess, GetStdHandle, VirtualAlloc, WriteConsoleA, WriteConsoleW gibi fonksiyonlar elle bildiriliyor
  • Inline assembly’de dış parantezler süslü parantez gibi ele alınıyor
    • if gibi açılış parantezinden önce boşluk bırakılıyor
    • Her constraint satırı iki nokta üst üste ile başlıyor
  • Bahsedilen stilin küçük bir programda görülebileceği örnek olarak wordhist.c var
  • Biraz daha büyük örnek olarak mini programlama dili implementasyonu asmint.c var

1 yorum

 
GN⁺ 2023-10-10
Hacker News yorumları
  • #define sizeof(x) (size)sizeof(x) için dış parantezlerin gerekmeyeceğini düşünmüş gibi görünüyor, ama çok küçük bir istisna var
    Cast işlemi çarpmadan daha yüksek önceliğe sahip olduğundan sizeof(x) * 3, (size)sizeof(x) * 3 olarak güvenli biçimde çalışır
    Ancak (size)sizeof(x)[y] ifadesinde dizi indeksleme cast işleminden önce uygulanır; yani ((size)sizeof(x))[y] değil, (size)(sizeof(x)[y]) olur
    Gerçek kodda sizeof(x) üzerinde indeksleme yapmanız pek olmaz, ama C integer[pointer] kullanımını pointer[integer] ile aynı anlama gelecek şekilde kabul ettiğinden, bu makro parantez eksikliği yüzünden derlenip yanlış çalışabilir
    Daha temelde, signed size’ın daha iyi olduğu iddiasına da katılmak zor. Yazar unsigned size’ın kusurların kaynağı olduğunu söylüyor, ama verilen kodda da count negatifse belleği bozan bir hata var
    İşaretsiz tamsayılarda negatif adetler ifade edilemez; taşma olsa bile çok büyük pozitif bir sayıya dönüşür ve mevcut kontrole takılır. Kişisel olarak işaretsiz tamsayı kullanıp mümkün olduğunca aralık kontrolü yapan sarmalayıcılar ile taşma durumunda programı durdurmayı tercih ederim

    • _Bool semantiği aslında hoşuma gidiyor
      Çünkü if (flags & FLAG_ALLOCATED) içinde düzgün çalışan bir ifadeyi _Bool need_free = flags & FLAG_ALLOCATED; gibi bir boolean değişkene çıkarabilirsiniz
      flags & FLAG_ALLOCATED, ayarlı olduğunda 1 değil, sıfır olmayan herhangi bir değer olabilir; _Bool bunu 1’e normalize eder. int olarak alırsanız if (need_free) geçer, ama if (need_free == true) başarısız olabilir
      Dezavantajları da var. Refactoring sırasında _Bool’a örtük dönüşümün işe yarar bir şey yaptığını gözden kaçırırsanız if ((flags & FLAG_ALLOCATED) == true) gibi hatalı bir koda dönüşebilir
      Ayrıca diskten bir struct okurken veya içine rastgele baytlar doldururken _Bool alanı 0 ya da 1 değilse tanımsız davranış riski vardır
    • Aslında (size)(sizeof(x)[y]) de birçok kişiye şaşırtıcı gelir, ama (size)(sizeof ((x)[y])) ile aynıdır
      sizeof bir fonksiyon değil, tekli operatördür; indeksleme ve fonksiyon çağrısı sizeof’tan daha yüksek önceliğe sahiptir. Bu yüzden sizeof sonrasında boşluk bırakmayı ve yalnızca gerektiğinde operandın etrafına parantez koymayı tercih ederim
      https://en.cppreference.com/w/c/language/operator_precedence
      Makroyu doğru yazmak için #define sizeof(x) ((size)(sizeof (x))) olur
    • İyi yakalamışsınız. Çıkarılacak ders şu: Bir makro tanımı tek bir token’a genişlemiyorsa her zaman parantezle sarılmalı. C’nin öncelik kuralları gerçekten karmaşık
  • Kendi tiplerini tanımlamak bana bir adım fazla ileri gitmek gibi geliyor
    C tiplerine zaten aşina olan biri bile bir programı anlamak için ayrıca kendine özgü bir sistemi öğrenmek zorunda kalıyor. Boyutu açıkça belirtmek mantıklı; bu yüzden uint yerine uint32_t kullanmak gibi yaklaşımları anlıyorum
    Bu tür tipler uygun header’da tanımlı olmalı; C’yi uzun süredir kullanmadığım için yanılıyor da olabilirim

    • Pratikte C’de int 32 bittir
      16 bit hedeflerde değil elbette, ama 5 MB’lık bir programı gerçekten 16 bite mi port edeceksiniz? Böyle kaygılar genelde zahmete değmez
      Sorun long’dur. Bazı makinelerde 32 bit, bazılarında 64 bit olduğu için kafa karıştırır. Neyse ki long long her zaman 64 bittir; bu yüzden long’u bırakmak yeterli
      char 8 bit, short 16 bit, int 32 bit, long long 64 bit; hepsi bu. C’de int boyutu yüzünden bitmek bilmeyen zaman harcadık
    • Yazar bunun kişisel kodlama stili olduğunu özellikle belirtmişti. Açıkçası standart tipler fazlasıyla uzun; bu kişinin sunduğu kısa listenin geçmişte benimsenmiş olmasını isterdim
    • Biraz şaka yollu söylersek, programlamanın önemli bir kısmı başkalarının tip sistemleriyle uğraşmaktır
      C’yi sık kullanan biri için burada geçen kısaltmalar tanıdıktır ve özel bir tip sistemi sayılabilecek bir şey için oldukça zariftir. Rust’ı hatırlatıyor
    • Bu tipler stdint.h içinde var
      Birçok projenin bu dosyayı zahmetle yeniden oluşturduğunu görmek beni hep şaşırtıyor
      Standart tipleri yeniden kendi isimleriyle adlandırıp kullanmak okuyucu için yorucu. Eskiden bir C++ projesinde koleksiyonlar, referanslar ve bileşik nesneler için bolca typedef kullanılmasını sorduğumda, daha anlaşılır hale geldiği yanıtını almıştım
      Daha sonra o kişinin monitörünün yanında bir typedef kopya kâğıdı asılı olduğunu gördüm
    • Kendi tamsayı tiplerini tanımlamak kaynak kısıtlı platformlarda anlamlıdır
      Sıkça dim_t gibi tipler görürsünüz; kullanıma göre 32 bit ya da 64 bit olurlar. 64 bit platformlarda bile pointer sıkıştırma yapılarında 32 bit tamsayılar sık kullanılır
      Örneğin kendi heap’inizi ayırıp yalnızca 32 bit offset saklarsanız, 4 GB altındaki iş yüklerinde bellek kullanımı yarıya iner; önbellek yerelliği de iyileştiği için performans artar
  • C’nin yerleşik teamüllerini kişisel tercih yüzünden bir kenara atmak biraz aşırı görünüyor
    uint8_t ya da int32_t yerine u8, i32 kullanmak birkaç karakter kazandırabilir ama başkaları kodu okurken kafa karıştırabilir
    Null ile sonlanan dizgiler yerine özel bir string tipi kullanmak da C’nin bu tür dizgiler etrafında tasarlandığı düşünülürse iş birliğini zorlaştırıyor gibi
    windows.h eklemeden Win32 API prototiplerini doğrudan yazmak derleme süresini azaltabilir, ama iyi bakımlı bir otoyol varken orman yolundan gitmek gibi. Bunların önemli bir kısmı, herkesin rahatça ele alabileceği C kodundan çok kişisel tercihe yakın görünüyor

    • u8 ya da i32, yazılacak tuş sayısından tasarruf etmek için değil, okurkenki algısal yükü azaltmak için
      Uzunluk ve kısalık tartışmalarında hep ortaya çıkan “tuş vuruşu sayısı” argümanı oldukça kusurlu. Kısalığın yalnızca hızlı yazmaya yaradığı, uzun ifadelerin ise okumak için her zaman daha iyi olduğu inancı yanlış
      Uzun ifadelerin anlamada avantajları var ama kısalığın da avantajları var; iki taraftan hiçbiri açık ara kazanan değil. Sadece farklı ödünleşimler
    • u16 gibi adlar yaygın kullanılıyor ve programcıların kafasını karıştırma olasılığı düşük
      Asıl çöküş noktası, iki farklı programın kendi u16 tanımlarını yapıp bunları başlık dosyalarında dışa açması ve üçüncü bir programın iki başlığı aynı anda dahil etmesi
      Ad alanı eklenmiş kütüphane tipleri libname_u32 gibi bir biçime dönüşüyor; o noktaya gelince libname_ öneki yerine doğrudan uint32_t kullanmak istiyorsunuz
    • Yetkin bir C programcısının u8 ya da i32 görüp gerçekten kafasının karışması ihtimali olsa olsa teorik; biraz korkuluk safsatası gibi duruyor
      Sinirlenebilir ama bu kafa karışıklığı olmaz. Rich Hickey’nin dediği gibi, okumayı öğrenmeden önce her şeyin okunması zordur
  • 32 bit boolean kullanmanın yeni başlayanlara bellek israfı gibi görünebileceği söyleniyor; öyleyse ben de yeni başlayanım herhalde
    8 bit bool’dan daha kötü olmadığı birkaç durum duydum ama gerçekten daha iyi olduğu bir durum göremiyorum. Bir struct içinde bitişik boolean’lar varsa ya da bir fonksiyonun boolean değişkeni register’dan atılıp stack’e konursa yine bellek israfı olur
    Birkaç bayt bile olsa neden bilerek kötümserleştirme yapıldığını anlamıyorum. Daha büyük boyut kullanarak ne kazanılıyor?

    • Tamamen mimariye ve CPU’ya bağlı, ama geçmiş deneyimde net örnek sayısal işleme işleriydi
      Döngü başına bir struct’ın başında koşul değeri, arkasında da 512, 1024, 2048 örnek değeri vardı; junior biri alan kazanmak için struct’ı paketledi ve koşul değerini 8 bitlik 1 bayt yaptı
      Bu “iyileştirilmiş” kod Intel çiplerde throughput’u yaklaşık 10 kat düşürdü, SPARC RISC mimarisinde ise BUS ERROR verdi
      Struct başlığı paketlenince veri dizisi hizasız kaldı; Intel sessizce iki 32 bit word’ü alıp birleştirmek zorunda kaldı, SPARC ise hizasız veriye beklendiği gibi kızdı
      Uzun süreli dosya saklama değil de throughput’un önemli olduğu pipeline hesaplamalarında veriyi “alan tasarrufuna” göre paketlemek yerine mimari hizalamaya uydurmak bazen daha iyi olabilir
    • Genellikle kolay optimizasyon, struct alanlarını 32 bit sınırlarına göre padding ile hizalamaktır
      Neredeyse tüm derleyiciler bunu yapar; bu yüzden “struct alignment/padding” diye aratabilirsiniz. Derleyici zaten boş alan bırakacaksa o belleği bizzat kullanmak daha iyidir; aksi halde performans kaçabilir
      Daha kesin söylemek gerekirse her alan, kendi boyutuna ya da wordline boyutuna bölünebilen bir adreste olmalı; tüm struct da en büyük alan boyutunun katı olacak şekilde padding almalı. Pratikte bu çoğunlukla 32 bit hizalama anlamına gelir
      Referans: http://www.catb.org/esr/structure-packing/
    • Gerçek bool tipini kullanırsanız değer false olan 0 ya da true olan 1 değilse sanitizer uyarı verir
    • Bilgisayar mimarileri çoğunlukla 32 bit ve üzeri hizalı erişim için optimize edilmiştir. Kazanç genelde, ama her zaman değil, performanstır
    • Bir boolean değişkenin stack’e atıldığı ve o 3 baytın önemli hale geldiği bir fonksiyon örneğini merak ediyorum
  • Struct döndürme ve çıktı parametreleri hakkındaki iddiaya katılmıyorum
    Hata döndürebilen fonksiyonları birleştirmeyi çok daha zor hale getiriyor ve her yere tipler yayılıyor. Gerçekte neredeyse her fonksiyon başarısız olabilir; özellikle bellek yetersizliğini de ele alıyorsanız öngörülebilir hata döndürme stili daha önemli

    • Bellek yetersizliğini neredeyse kimse ele almıyor
      Çok zor ve getirisi de pek yok. O noktada programlama stili seçimlerinden çok daha farklı sorunlar ortaya çıkıyor
    • Anlamlı bir struct unpacking mümkün olsaydı, sıradan errno ve çıktı parametresi semantiğini elde edebilirdik; ama C’de yok
      Yine de C’de optional değerleri birleştirmek her zaman biraz sancılıydı. Exception kullanmıyorsanız, dillerde exception ve monad iki ana seçenek gibi görünüyor; ama ikisi de C’ye uymuyor ve çoğu C programcısının felsefesine de uymuyor
      Basit bire bir çağrılar için makro denenebilir ama sınırları var. C++ berbat olsa bile C++ optional, if(foo(x,y, out1, out2) != WHATEVER_LIBRARY_OK) { ... } yazmaktan daha keyifli
    • Option ya da sum type döndürmek doğru olanı, ama C’de yazması gerçekten zahmetli
      Her fonksiyon çağrısına if (thing(...)) goto fail ekleme kalıbı da pek harika görünmüyor, ama Go tarafı bunu seviyor gibi
      Ya da thread_local mylibrary_errno var; kütüphane içinde bu gerçekten doğru yöntem olabilir, sınırda ise enum dönüş değerine dönüştürülür
  • “signed sizes are the way” ifadesinde okumayı bırakmak yeterliydi demek istiyorum
    signed size çok şaşırtıcı bir soyutlama sızıntısı ve felakete davetiye çıkaran bir yaklaşım
    constun pratik bir rolü olmadığı ve hiç hata yakalatmadığı iddiasına da katılmak zor. İnsanlar giriş tamponu ile çıkış tamponunu sık sık karıştırıyor; const bunu anında ortaya çıkarır
    Giriş noktası dışında tüm fonksiyonları static yapmak da, hata ayıklarken değişkeni ya da fonksiyonu bulamayıp yazara lanet okumanıza yol açabilir
    Yapı döndürmeyi tercih etmek, yanlışlıkla stack pointer döndürüp büyük bir güvenlik açığı oluşturmayı kolaylaştırır. Çıkış tamponu geçirmek sahiplik semantiğini netleştirir
    Bu tavsiye çoğunlukla 64 bit sistem kodu yazan biri için fena olmayabilir, ama 32 bit gömülü alanda hızla sorun çıkarabilir

    • Bjarne Stroustrup, signed sizeı savunan ayrıntılı bir not yazmıştı
      https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p14...
    • consttan hoşlanmayanlar asla tatmin olmayacak; bu yüzden gereken yerde const kullanıp gerektiği kadar yaydıktan sonra şikâyetleri görmezden gelmek yeterli
      Onlar kaldırırsa tekrar eklersiniz. Her zaman önce onlar yorulur. 25 yıldır böyle yapıyorum ve hâlâ buradayım
      static araçlara göre değişebilir. Yaklaşık 15 yıl önce varsayılan olarak static kullanmaya ve her yerde size_t kullanmaya geçtim; hâlâ sorun yaşamadım
  • Benim deneyimimin başka bir yöne gitmiş olması ilginç
    https://dlang.org/blog/2023/10/02/crafting-self-evident-code...
    Yazı D odaklı yazılmış olsa da ilkeler C için de geçerli

    • Keyifle okudum
      Koşul ifadelerini doX() ve doZ() içine taşıma kısmı ilginçti. Bunun her zaman doğru olup olmadığından emin değilim; soyutlamanın nereye konduğuna ve koda dair zihinsel modele bağlı
      Örneğin deleteRecords();, if let x = deadRecords() deleteRecords(x);dan daha iyi değil. İkincisi daha dağınık görünse de, en başta bunun silme değil budama olduğunu göstermesinin bir değeri var
      Fonksiyonu pruneDeadProjects() gibi akıllıca yeniden adlandırırsanız sorun yok; ama yalnızca koşulu fonksiyonun içine taşımak bağlamı tehlikeli hâle getirebilir ve sızdıran bir soyutlama olabilir
  • Tüm yapılarda typedef kullanmak sadeliğe yardımcı olduğu için katılıyorum
    typedefin bolca kullanılabileceğini düşünüyorum. Ancak yalnızca hedefin kendisine typedef yapmak, pointer’a yapmamak daha iyi. Pointer gerekiyorsa her zaman (type *) yazılabilir
    Özellikle fonksiyon pointer’larında, fonksiyon pointer’ını değil fonksiyonun kendisini typedef etmek gerekir. Böylece fonksiyon bildirimlerinde de o typedef kullanılarak parametre tipi denetimi alınabilir ve fonksiyon imzası değiştiğinde tüm bildirimleri düzeltmek gerekmez
    Çoğu C kod tabanı bunu yanlış yapıp fonksiyon pointer’ını typedef ediyor ve yine de o pointer tanımına uygun fonksiyon bildirimlerini elle yazmak zorunda kalıyor
    Yapıları dönüş tipi olarak kullanma yaklaşımı konusunda hâlâ ikna olmadım. Sayısal hata kodunu dönüş değeri yapmak, diğer dönüş değerlerini ise çıkış parametreleriyle almak bana daha uygun geliyor

    • Opak yapılarda tüm alanları özel olan bir sınıfı taklit etmek için typedef kullanmayı, basit veri yapılarında ise struct kullanmayı tercih ederim
      Sınıflara yalnızca fonksiyonlarla erişilmeli, yapılara ise doğrudan erişilebilmelidir
      Bu genel olarak C/POSIX standart geleneğine daha yakın. Örneğin pthread_t ile struct stat arasındaki fark gibi
    • Pointer’ın kendisine typedef yapılmaması gerektiğine katılıyorum
      SDL_net gibi şeylerden tam da bu yüzden hoşlanmıyorum. Aslında pointer olduğu hâlde değer tipiymiş gibi typedef edilmiş
      Niyeti anlıyorum ama oldukça rahatsız edici bir yöntem
  • Bu yazının pek çok kısmı mantıklı geliyor
    Arm64 için bare-metal OS yazmaya başladım; henüz erken aşamada ama benzer şeyler yapıyorum. Pascal string’leri kullanıyor, tür adlarını da değiştirdim. Yalnız i8 değil, int8 tarzı
    Gerçek yazılımları port etme niyetim olmadığına hızlıca karar verdiğim için standart C kütüphanesi fonksiyonlarına ya da geleneklerine uymam gerekmiyor. Bu sayede daha özgürce deney yapabiliyorum
    C, her byte’ın değerli olduğu dönemlerin yükü fonksiyon adlarına kadar kalmış olacak kadar eski bir dil. Bundan sıyrılmak güzel; bu yazıdaki içerik ve çeşitli küçük ad değişiklikleri epey temiz bir toparlama gibi hissettiriyor

    • Standart C kütüphanesi fonksiyonlarına ya da geleneklerine uymak zorunda olmamak, bunun sadece hobi olarak yapılan bir şey olduğu ve GNU gibi büyük, profesyonel bir şey olmayacağı anlamına mı geliyor?
    • Sembollerde de byte tasarrufu yapıldığını hiç düşünmemiştim
  • typedef float f32;, typedef double f64;, floatın 32 bit ve doubleın 64 bit olduğunu varsayan tehlikeli bir dayanak gibi görünüyor
    OpenCV float16_t tanımlar, CUDA yarı hassasiyetli kayan noktayı uygular ve mikrodenetleyiciler bunu birbirinden farklı şekillerde gerçekleştirebilir
    C++23 sabit genişlikli kayan nokta türlerini getiriyor, ancak C’de bunu zorunlu kılmanın bir yolunu bilmiyorum. Derleme zamanında veri kaybı olmadığını kontrol eden bir makro koymak daha iyi görünüyor
    Genel olarak, başkalarının da söylediği gibi, kısa olmasa bile okunabilirlik için bazı şeyleri varsayılan hâliyle bırakmak daha iyi olabilir
    [0] https://docs.opencv.org/4.x/df/dc9/classcv_1_1float16__t.htm...
    [1] https://docs.nvidia.com/cuda/cuda-math-api/group__CUDA__MATH...
    [2] https://en.cppreference.com/w/cpp/types/floating-point