- Yalnızca ISO C standardına uyan kod nadirdir; gerçek dünyadaki C kod tabanları, ek işlevler ve derleyici·kütüphane bazlı boşlukları aşmak için standart dışı uzantılara dayanır
- Kullanışlı bir C derleyicisinin
<stdio.h>gibi sistem başlıklarını işleyebilmesi gerekir, ancak glibc__attribute__((packed)),#include_nextgibi GNU uzantıları ve çeşitli varsayımlar nedeniyle engel oluşturur - SDL’nin byteswapping mantığı, ISA makroları varsa inline assembly yolunu seçebilir; bu da GCC·clang dışındaki derleyicilerden bile GCC tarzı uzantılar beklenmesine yol açabilir
- OpenBSD ve Gnulib’in
extern inlineişleyişi, C99 ile GCC anlam farkı, platforma özgü dallanmalar ve_FORTIFY_SOURCEkoşulları nedeniyle inline semantiği uyumluluğunu karmaşıklaştırır - Küçük C derleyicileri, upstream yama gönderme, downstream yama taşıma, özel korumalar elde etme ya da GCC uyumluluğunu taklit etme arasında seçim yapmak zorundadır; özellik test makrolarının yaygınlaşması daha iyi bir yön gibi görünür
glibc başlıklarının oluşturduğu ilk engel
- Kullanışlı bir C derleyicisi olmak için sistem C kütüphanesi başlıklarını önişleyip ayrıştırabilmek gerekir;
<stdio.h>işlenemiyorsa hello world bile zor geçer - GNU/Linux ortamında bu engel doğrudan glibc’ye çıkar
- glibc, neredeyse tüm libc başlıklarının dolaylı olarak içerdiği sys/cdefs.h içinde derleyicinin önceden tanımladığı makroları denetleyerek hangi uzantıların desteklendiğini belirler
- Desteklenmeyen uzantılar ilgili tanımların kaldırılmasıyla ele alınır, ancak bu uyumluluk mantığı pratikte yine de bozulabilir
-
struct epoll_eventve__attribute__((packed))- Linux’un
sys/epoll.hiçindekistruct epoll_event, GNU__attribute__((packed))kullanan bir packed struct yapısıdır - Bu öznitelik 64 bitte yapı yerleşimini değiştirdiği için yok sayılırsa ABI bozulur
- Derleyicinin
__attribute__((packed))uygulaması tek başına yeterli değildir sys/cdefs.hiçinde GCC, clang ya da tcc değilse__attribute__(xyz)ifadesini boş makro yapan kod bulunur- Sonuç olarak başka derleyiciler packed özniteliğini desteklese bile glibc başlıklarında bu öznitelik kaldırılabilir
epollbaşlığı Linux’a özgü olduğu için burada C standardı taşınabilirlik ölçütlerini birebir uygulamanın zor olduğu da savunulabilir
- Linux’un
-
limits.hve#include_nextstddef.h,stdint.h,limits.h,float.hgibi bazı C başlıkları freestanding uygulamalarda da gerektiğinden derleyici tarafından sağlanmalıdır- POSIX, standart C sabitlerine ek olarak POSIX’e özgü sabitlerin de
limits.hiçinde tanımlanmasını ister; bu nedenle derleyicininlimits.hdosyasının üzerinde platforma özgü birlimits.hgerekir - glibc’nin
<limits.h>başlığı GNU C değilse ANSIlimits.hdeğerlerini doğrudan tanımlar, GCC ortamında ise derleyici başlığını#include_next <limits.h>ile alır - Bu yapı, GCC’ye özgü builtin
limits.hdosyasının belirli makroları tanımladığını varsayar ve ayrıca#include_nextuzantısına dayanır - clang de bu yapıyı özel olarak ele almak zorundadır
SDL’nin özellik algılama ve inline assembly sorunu
SDL_endian.hiçindeki byteswapping işlevleri, mümkün olduğunda derleyici builtin’lerini ya da inline assembly kullanır; son çare olarak normal bit işlemi uygulamasına düşer- Algılama mantığı kabaca şu sırayla çalışır
- GCC veya clang ise ve
__has_builtin(__builtin_bswapX)varsa builtin kullanılır - MSVC 8.0 veya üstüyse MSVC intrinsic
#pragmakullanılır __x86_64__gibi ISA’ye özgü makrolar tanımlıysa inline assembly kullanılır- Bunların dışında normal bit işlemi uygulaması kullanılır
- GCC veya clang ise ve
- GCC ya da clang olmayan bir derleyici makul nedenlerle ISA’ye özgü predefined macro’ları tanımlıyorsa bu sıra sorun çıkarabilir
- İlgili derleyici bswap builtin’ini ve
__has_builtinözel işlemini sunsa bile mantık gereği GCC tarzı inline assembly kullanmaya çalışabilir - Sonuç olarak yapı, bilinmeyen bir derleyicinin de GCC tarzı inline assembly desteklediğini varsaymış olur
OpenBSD libc ve extern inline karmaşası
- OpenBSD’nin bazı başlıkları, derleyicinin optimizasyon sırasında isteğe bağlı kullanabileceği inline işlev tanımları içerir
- Bu işlevler
__only_inlinemakrosuyla tanımlanır; derleyici bunları gerçekten inline etmezse dış sembole geri dönülmesi gerekir - Yani extern linkage’e sahip inline işlevlere ihtiyaç vardır
-
C99 inline ile GCC inline anlam farkı
inlineC99’da tanımlanmıştır, ancak standart davranış C99 öncesi standart dışı GCC davranışıyla çakışır- Başlık içindeki inline tanım, işlev gövdesiyle birlikte
extern inlinekullanmalıdır; bu durumda gerçek exported function üretilmez - translation unit içinde işlev tanımını export etmek için bildirim yalnızca
inlineile verilmelidir inlineın anlamı C++ ile C arasında da farklıdır- Bu farklar Youtao Guo’nun yazısında ayrıntılı ele alınıyor
-
OpenBSD’nin
__only_inlineyaklaşımı- OpenBSD, GCC inline semantics’e dayanır
- GCC sürüm farklarını örtmek için sys/cdefs.h içindeki
__only_inlinemakrosu, yeni GCC sürümlerinde eski gnu89 inline semantics’i açık bir__attribute__ile seçer - GNU dışı derleyicilerde
__only_inlinestaticlinkage ile tanımlanır - Bunun sonucu olarak işlevler çelişen linkage’larla bildirilip tanımlanarak bozulabilir
-
_ANSI_LIBRARYkaçış yolu- OpenBSD
_ANSI_LIBRARYmakrosuna saygı gösterir - Bu makro tanımlanırsa
signal.hgibi standart başlıklardaki sorunlu__only_inlinetanımları tamamen atlanır - Optimize sürüm elde edilmez ama en azından derleme çalışır
- OpenBSD
-
Gnulib’in
extern inlineuyumluluk kodu- Gnulib içindeki
extern inlineuyumluluk kodu, Guile ve nano derlenirken de karşımıza çıkar - extern-inline.m4, bu C köşe vakasının bozuk ve tuhaf uygulamalarını ele almak için karmaşık koşullu dallanmalar içerir
- Bu koşullara Apple, DragonFly, FreeBSD, GCC, clang, PCC, HP cc, PGI, SunPro C,
_FORTIFY_SOURCE,__GNUC_STDC_INLINE__,__GNUC_GNU_INLINE__gibi ortam farkları yansıtılmıştır
- Gnulib içindeki
Android bionic’in clang varsayımı
- bionic Android’in libc’sidir ve başlıkları GCC’den çok clang varsayımı üzerine kuruludur
- bionic başlıkları, nullability checks için
_Nonnull,_Null_unspecifiedgibi clang’e özgü uzantıları yoğun biçimde kullanır - Bu tür makroları komut satırı bayraklarıyla
#defineederek etkisizleştirmek çok zor değildir - Android telefonu Termux üzerinden yerel bir aarch64 geliştirme ortamı olarak kullanırken bu sorun bionic başlıklarında görünür hâle gelir
_Null_unspecifiedaynı zamanda__BIONIC_COMPLICATED_NULLNESSolarak da anılır; ilgili tanım bionic’in sys/cdefs.h içindedir
Küçük C derleyicilerinin karşılaştığı seçenekler
- Yalnızca ISO C standardına uyan kod gerçek hayatta nadirdir; birçok C kod tabanı standart dışı davranışlara ve dil uzantılarına dayanır
- Bu bağımlılık yalnızca ek işlevlerden değil, derleyici ve kütüphaneler arasındaki farklı hata ve eksiklikleri aşma çabasından da doğar
- Birden fazla ortamı desteklemeye çalışan kod tabanları önişlemci kontrollerine ve korumalara dayanır, ancak bu yaklaşım kolayca bozulur ve yönetmesi zordur
- antcc gibi bir C derleyicisi geliştirirken bu uyumluluk sorunları tekrar tekrar ortaya çıkar
- Pek çok açık kaynak proje, zorunlu olmayan konularda bile derleyiciye özgü standart dışı uzantı ve davranışlara dayanırsa alternatif derleyicilerin yükü büyür
- Aynı zamanda her geliştiriciden küçük ve az bilinen derleyiciler dahil birden fazla derleyicide C kodunu test etmesini istemek de gerçekçi değildir
- C taşınabilirliği zaten başlı başına yeterince zordur
- Derleyici yazarının önündeki olası seçenekler dört tanedir
- Uyumsuzluğu upstream’de yamamaya çalışmak
- Yeterince tanınır olup geliştiricilerin özel
#ifdefkontrolleri ve temel testler eklemesini sağlamak - Sorunu downstream’de ele alıp yamalar veya ayrı yamalar dağıtmak
- Belirli bir GCC sürümüymüş gibi davranıp ilgili uzantıları uygulamak
- Upstream yamalar pek kazanılacak bir mücadele gibi görünmüyor; downstream yama en kolay yol
- Çok sayıda kod tabanını kullanıcıya ve geliştiriciye en az kafa karışıklığıyla desteklemek için GCC uyumluluğunu taklit etmek gerçekçi görünüyor, ancak uygulama yükü yüksek
- clang, GCC 4.2.1 uyumlu olduğunu göstermek için
__GNUC__=4,__GNUC_MINOR__=2,__GNUC_PATCHLEVEL__=1tanımlar - Bugün clang ayrı bir destek hedefi gibi görünse de Linux çekirdeğini clang ile derlenebilir hâle getirmek için her iki projede de yama gerekecek kadar büyük emek harcanmıştır
GCC makroları ve yetişme sorunu
- GCC’ymiş gibi davranma yaklaşımının da sorunları vardır
- Pek çok kod tabanı yalnızca
#ifdef __GNUC__kontrolü yapar ve sürüm bakmadan yeni GCC uzantılarını kullanabilir - Bu durumda alternatif derleyici sürekli yetişmek zorunda kalır
- clang’in 4.2.1’den daha yeni GNU uzantılarını desteklemesine rağmen
__GNUC__makro değerini yükseltmemesinin nedenlerinden biri de budur - Konuyla ilgili arka plan LLVM’nin
__GNUC__minor sürümünü artırma tartışmasında bulunabilir
Daha iyi yön ve mevcut durum
- İdeal olarak derleyiciye özgü korumalar ve sürüm kontrolleri yerine özellik test makroları daha yaygın kullanılmalıdır
- Kullanışlı özellik test makroları arasında
__has_builtin,__has_feature,__has_attributebulunur __STDC_NO_VLA__gibi standart makroların yaklaşımı da daha fazla kullanılabilir- Bugünkü *NIX dünyasında, iyi ya da kötü, temel durum GCC/clang yarı-ikili düzenidir
- Bağımsız küçük C derleyicilerinin geliştirilmesi de sürüyor
1 yorum
Lobste.rs görüşleri
(kefir derleyicisinin yazarı)
<sys/cdefs.h>içindeki__attribute__sorunu deneyimime göre en can sıkıcı problemlerden biri.epoll'u, yaygınpackedyapıları, kurucuları ve sembol görünürlüğünü bozuyor; bu yüzden kefir ile birlikte şu monkey patch başlığını dağıtmak zorunda kaldımİdeal değil ama muhtemelen en gerçekçi yöntem bu ve gerçekten de harici test paketlerinde özel yamaların çoğunu kaldırabilmemizi sağladı
Bir başka başarısızlık türü de hatalı alternatif kod. Bazı projeler derleyiciyi tespit edip ona göre davranmaya çalışıyor ama alternatif derleyicilerde yeterince test edilmediği için fallback kodu hatalarla dolu oluyor ya da düzgün bakım görmüyor. Bir derleyici yazarı açısından bu, doğrudan “desteklenmeyen derleyici” diyerek başarısız olmaktan çok daha sinir bozucu. Çünkü örneğin program ile önceden derlenmiş kütüphane arasında tamsayı
typedefgenişliği uyuşmazlığı gibi garip yanlış derlemeleri bizzat debug etmek zorunda kalıyorsunuz$TERMdeğişkeninixterm-256coloryapıp xterm gibi davranmazsanız her şey dağılıyorBunun nasıl çözüleceğini gerçekten bilmiyorum. Sonunda tek yol projemizin yeterince yaygınlaşıp tanınması mı diye düşünüyorum. Ne kadar kolay!
Derleyici tespiti fallback'lerinin düzgün yönetilmemesi yüzünden oluşan garip yanlış derlemeleri ben de birkaç kez yaşamış gibiyim; gerçekten sinir bozucu
cproc'u çoğunlukla linux-musl üzerinde geliştirdiğim için glibc'nin başka derleyicilerde
__attribute__'u devre dışı bıraktığını bilmiyordum ama bu gerçekten oldukça kötü bir durum. Yorumlarda, attribute kullanımının yok sayılmasının sorun olmayacağı yazıyor ama çoğu uygulama kodunun dolaylı olaraksys/cdefs.heklediğini ve burada yok sayılmaması gereken attribute'lar kullanılabileceğini hesaba katmıyorpackeddışındaalignedveconstructorda sık kullanılıyorBunun bir issue tracker'da raporlanıp raporlanmadığını merak ediyorum.
cdefs.hiçindeki attribute kullanımlarının çoğu zaten__glibc_has_attributeile korunuyor gibi görünüyor; bu yüzden__attribute__'u topluca devre dışı bırakmanın pratikte ne sağladığını ve kaldırılıp kaldırılamayacağını da merak ediyorumlibc başlıklarının kullandığı bazı özelliklerde sorun, derleyicinin bunları desteklediğini düzgün şekilde belirtmesinin bir yolunun olmaması.
__has_attributeya da__has_builtingibi yollarla ortaya çıkmayan özellikler bunlar; aklıma gelen örnek__asm__etiketi. NetBSD bunu sembol adını değiştirmek için kullanıyor ve__GNUC__ya da__PCC__yoksa#errorveriyor. Ama desteklenmiyorsa sadece denemeye izin verip başarısız olmasından başka ne önerilebilir, bilmiyorum__builtin_va_listile ilgili sorunlar da yaşadım. libc,__GNUC__olmadan defineva_listtovoid *yapabiliyor ya da hatta çakışan tanımlar koyabiliyor. Bu da__has_builtinile test edilemiyor.__has_builtin(__builtin_va_arg)yeterince iyi bir test olabilir ama macOS'ta bunun nasıl düzelttirilebileceğini gerçekten bilmiyorum/usr/include/sysve/usr/include/bitsaltında__attribute__kullanımını hızlıca aradım; korunmayan çok sayıda kullanım vardı. Çoğu__format__,__aligned__,__noreturn__idi; yani bunların da düzeltilmesi gerekirglibc genel olarak GCC dışı derleyicilerle uyumluluğu önceliklendirmiyor gibi görünüyor; o yüzden böyle yamaları kabul ederler mi emin değilim. Bu yılın başında sistem yükseltmesinden sonra glibc, Linux başlıklarına korunmasız
__SIZE_TYPE__kullanımı ekledi ve bu da derleyicimin bazı projeleri derleyememesine yol açtı. Rapor ettim ama hâlâ düzeltilmedi; sonunda GCC'ye uyum sağlamak için__X_TYPE__tarzı önceden tanımlı makrolar ekledim__asm__etiketi sorununa iyi bir çözüm pek gelmiyor aklıma. Ama asm isim değiştirme gerçekten baştan beri çalışmak için %100 gerekliyse, derleyici kontrolü yapmak yerine sadece denemek ve başarısız olmasına izin vermek daha iyi olabilir__builtin_va_listoldukça ciddi.__has_builtin(__builtin_va_list)çalışır diye beklerdim ama görünüşe göre öyle değilmiş