2023 Sonu İtibarıyla Kişisel C Kodlama Stilim
(nullprogram.com)- 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,s8gibi kısa takma adlar ileconstvestructkullanı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
dataveleniçerens8fat pointer ile ele alınıyor; Win32 ve UTF-16 ortamları içinc16ves16birlikte 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
okayarlanan 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
- Örnek:
- 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
_tsoneki artık kullanılmıyor; bugün görsel olarak dikkat dağıtan bir unsur gibi geliyor- signed tür öneki olarak
syerineitercih ediliyors, dizgi tür adları için ayrılmış tutuluyor
- Boyut türü için
isizeyerinesizekullanı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_Boolyerine 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
flagsdeğişkenine sıkıştırılıyor
c16, Win32’de gerekli olan UTF-16 karakterleri için bir türchar16_ttabanlı 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;byteise 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_tgibi 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
- Okurun bağlamı anlaması için
Makro ve assert kuralları
- Fonksiyon benzeri makrolarda küçük harf kullanılıyor
- Örnek:
countof(a),lengthof(s),new(a, t, n)
- Örnek:
- Sabitler için hâlâ
ALL_CAPStercih 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 ilenewdeğ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
assertmakrosuwhile (!(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-trapile UBSan trap moduna alınıp en azından-fsanitize=unreachableaçılıyor -funreachable-trapsile de teorik olarak mümkün, ancak yazıldığı tarih itibarıyla son birkaç GCC sürümünde bozuk
Bildirimlerde azaltılanlar
- Parametrelerde
constkullanı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
consthâlâ tercih ediliyor- Gerekirse
constcast ile kaldırılıyor
- Gerekirse
- Null işaretçi için literal
0kullanı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ş
restrictyalnı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
inlinekullanılmıyor- Çünkü her şey tek bir çeviri birimi olarak derleniyor
- Tüm struct’lar için
typedefyapılıyorstructanahtar 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
staticolarak bildiriliyor- Çünkü tek çeviri birimiyle derleme varsayılıyor
- Kısa tür adları,
constun kaldırılması vestructun 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
dataveleniçerens8dizgi türünü kullanmaya başlamak s8yapısı:u8 *datasize len
s8(s)makrosu, C dizgi literallerinis8dizgisine sarıyors8, fat pointer gibi değer olarak geçiriliyor ve değer olarak döndürülüyors8, fonksiyon öneki olarak da kullanışlı- Çünkü
strailesindeki adlar ayrılmış durumda - Örnek:
s8span,s8equals,s8compare,s8hash,s8trim,s8clone
- Çünkü
- 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
s16türü de kullanılıyorc16 *datavesize leniçeriyor- Makroda literale
uekleme 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 olanvalueile durum bilgisi olanokdeğ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
restrictout parametresine dönüştürebiliyor - Inline edilirse dönüş değeri overhead’i anlamını yitiriyor
- Çağrı kuralı bunu gizli bir
- 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
oktrue olarak ayarlanıyor
- Statik veri ve
s8/s16makroları 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__attributetercih ediliyor- Sondaki
__soneki aşırı ve gereksiz görülüyor
- Sondaki
- Win32 sistem programlamasında
windows.hdahil 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_PTRyerineu32,b32,uptrgibi özel türlerle daha temiz uyum sağlıyor
- Win32 bildirim örneklerinde
W32(r) __declspec(dllimport) r __stdcallmakrosu kullanılıyorExitProcess,GetStdHandle,VirtualAlloc,WriteConsoleA,WriteConsoleWgibi fonksiyonlar elle bildiriliyor
- Inline assembly’de dış parantezler süslü parantez gibi ele alınıyor
ifgibi 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.cvar - Biraz daha büyük örnek olarak mini programlama dili implementasyonu
asmint.cvar
1 yorum
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 varCast işlemi çarpmadan daha yüksek önceliğe sahip olduğundan
sizeof(x) * 3,(size)sizeof(x) * 3olarak güvenli biçimde çalışırAncak
(size)sizeof(x)[y]ifadesinde dizi indeksleme cast işleminden önce uygulanır; yani((size)sizeof(x))[y]değil,(size)(sizeof(x)[y])olurGerçek kodda
sizeof(x)üzerinde indeksleme yapmanız pek olmaz, ama Cinteger[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ışabilirDaha 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
countnegatifse 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
_Boolsemantiğ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 çıkarabilirsinizflags & FLAG_ALLOCATED, ayarlı olduğunda 1 değil, sıfır olmayan herhangi bir değer olabilir;_Boolbunu 1’e normalize eder.intolarak alırsanızif (need_free)geçer, amaif (need_free == true)başarısız olabilirDezavantajları 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ızif ((flags & FLAG_ALLOCATED) == true)gibi hatalı bir koda dönüşebilirAyrıca diskten bir struct okurken veya içine rastgele baytlar doldururken
_Boolalanı 0 ya da 1 değilse tanımsız davranış riski vardır(size)(sizeof(x)[y])de birçok kişiye şaşırtıcı gelir, ama(size)(sizeof ((x)[y]))ile aynıdırsizeofbir fonksiyon değil, tekli operatördür; indeksleme ve fonksiyon çağrısısizeof’tan daha yüksek önceliğe sahiptir. Bu yüzdensizeofsonrasında boşluk bırakmayı ve yalnızca gerektiğinde operandın etrafına parantez koymayı tercih ederimhttps://en.cppreference.com/w/c/language/operator_precedence
Makroyu doğru yazmak için
#define sizeof(x) ((size)(sizeof (x)))olurKendi 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
uintyerineuint32_tkullanmak gibi yaklaşımları anlıyorumBu tür tipler uygun header’da tanımlı olmalı; C’yi uzun süredir kullanmadığım için yanılıyor da olabilirim
int32 bittir16 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 kilong longher zaman 64 bittir; bu yüzdenlong’u bırakmak yeterlichar8 bit,short16 bit,int32 bit,long long64 bit; hepsi bu. C’deintboyutu yüzünden bitmek bilmeyen zaman harcadıkC’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
stdint.hiçinde varBirç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
typedefkullanılmasını sorduğumda, daha anlaşılır hale geldiği yanıtını almıştımDaha sonra o kişinin monitörünün yanında bir typedef kopya kâğıdı asılı olduğunu gördüm
Sıkça
dim_tgibi 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_tya daint32_tyerineu8,i32kullanmak birkaç karakter kazandırabilir ama başkaları kodu okurken kafa karıştırabilirNull 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.heklemeden 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üyoru8ya dai32, yazılacak tuş sayısından tasarruf etmek için değil, okurkenki algısal yükü azaltmak içinUzunluk 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
u16gibi adlar yaygın kullanılıyor ve programcıların kafasını karıştırma olasılığı düşükAsıl çöküş noktası, iki farklı programın kendi
u16tanı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 etmesiAd alanı eklenmiş kütüphane tipleri
libname_u32gibi bir biçime dönüşüyor; o noktaya gelincelibname_öneki yerine doğrudanuint32_tkullanmak istiyorsunuzu8ya dai32görüp gerçekten kafasının karışması ihtimali olsa olsa teorik; biraz korkuluk safsatası gibi duruyorSinirlenebilir 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?
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
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/
booltipini kullanırsanız değerfalseolan 0 ya datrueolan 1 değilse sanitizer uyarı verirStruct 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
Çok zor ve getirisi de pek yok. O noktada programlama stili seçimlerinden çok daha farklı sorunlar ortaya çıkıyor
errnove çıktı parametresi semantiğini elde edebilirdik; ama C’de yokYine 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 keyifliHer fonksiyon çağrısına
if (thing(...)) goto failekleme kalıbı da pek harika görünmüyor, ama Go tarafı bunu seviyor gibiYa da
thread_local mylibrary_errnovar; 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;constbunu anında ortaya çıkarırGiriş noktası dışında tüm fonksiyonları
staticyapmak da, hata ayıklarken değişkeni ya da fonksiyonu bulamayıp yazara lanet okumanıza yol açabilirYapı 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
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p14...
consttan hoşlanmayanlar asla tatmin olmayacak; bu yüzden gereken yerdeconstkullanıp gerektiği kadar yaydıktan sonra şikâyetleri görmezden gelmek yeterliOnlar kaldırırsa tekrar eklersiniz. Her zaman önce onlar yorulur. 25 yıldır böyle yapıyorum ve hâlâ buradayım
staticaraçlara göre değişebilir. Yaklaşık 15 yıl önce varsayılan olarakstatickullanmaya ve her yerdesize_tkullanmaya geçtim; hâlâ sorun yaşamadımBenim 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
Koşul ifadelerini
doX()vedoZ()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 varFonksiyonu
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 olabilirTüm yapılarda
typedefkullanmak sadeliğe yardımcı olduğu için katılıyorumtypedefin bolca kullanılabileceğini düşünüyorum. Ancak yalnızca hedefin kendisinetypedefyapmak, 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
typedefkullanmayı, basit veri yapılarında isestructkullanmayı tercih ederimSı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_tilestruct statarasındaki fark gibitypedefyapılmaması gerektiğine katılıyorumSDL_net gibi şeylerden tam da bu yüzden hoşlanmıyorum. Aslında pointer olduğu hâlde değer tipiymiş gibi
typedefedilmiş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
i8değil,int8tarzı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
typedef float f32;,typedef double f64;,floatın 32 bit vedoubleın 64 bit olduğunu varsayan tehlikeli bir dayanak gibi görünüyorOpenCV
float16_ttanımlar, CUDA yarı hassasiyetli kayan noktayı uygular ve mikrodenetleyiciler bunu birbirinden farklı şekillerde gerçekleştirebilirC++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
_Floattürleri vartypedef _Float32 f32;typedef _Float64 f64;https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html