1 puan yazan GN⁺ 17 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • 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_next gibi 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 inline işleyişi, C99 ile GCC anlam farkı, platforma özgü dallanmalar ve _FORTIFY_SOURCE koş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_event ve __attribute__((packed))

    • Linux’un sys/epoll.h içindeki struct 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.h iç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
    • epoll başlığı Linux’a özgü olduğu için burada C standardı taşınabilirlik ölçütlerini birebir uygulamanın zor olduğu da savunulabilir
  • limits.h ve #include_next

    • stddef.h, stdint.h, limits.h, float.h gibi 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.h içinde tanımlanmasını ister; bu nedenle derleyicinin limits.h dosyasının üzerinde platforma özgü bir limits.h gerekir
    • glibc’nin <limits.h> başlığı GNU C değilse ANSI limits.h değ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.h dosyasının belirli makroları tanımladığını varsayar ve ayrıca #include_next uzantı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.h iç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 #pragma kullanı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 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_inline makrosuyla 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ı

    • inline C99’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 inline kullanmalı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 inline ile 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_inline yaklaşımı

    • OpenBSD, GCC inline semantics’e dayanır
    • GCC sürüm farklarını örtmek için sys/cdefs.h içindeki __only_inline makrosu, yeni GCC sürümlerinde eski gnu89 inline semantics’i açık bir __attribute__ ile seçer
    • GNU dışı derleyicilerde __only_inline static linkage ile tanımlanır
    • Bunun sonucu olarak işlevler çelişen linkage’larla bildirilip tanımlanarak bozulabilir
  • _ANSI_LIBRARY kaçış yolu

    • OpenBSD _ANSI_LIBRARY makrosuna saygı gösterir
    • Bu makro tanımlanırsa signal.h gibi standart başlıklardaki sorunlu __only_inline tanımları tamamen atlanır
    • Optimize sürüm elde edilmez ama en azından derleme çalışır
  • Gnulib’in extern inline uyumluluk kodu

    • Gnulib içindeki extern inline uyumluluk 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

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_unspecified gibi clang’e özgü uzantıları yoğun biçimde kullanır
  • Bu tür makroları komut satırı bayraklarıyla #define ederek 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_unspecified aynı zamanda __BIONIC_COMPLICATED_NULLNESS olarak 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 #ifdef kontrolleri 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__=1 tanı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_attribute bulunur
  • __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ın packed yapı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ı typedef genişliği uyuşmazlığı gibi garip yanlış derlemeleri bizzat debug etmek zorunda kalıyorsunuz

    • Benzer bir şey terminallerde de oluyor. $TERM değişkenini xterm-256color yapıp xterm gibi davranmazsanız her şey dağılıyor
      Bunun 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!
    • Monkey patch başlığı yaklaşımı slimcc'nin de kullandığı bir yöntem gibi görünüyor ve oldukça iyi bir uzlaşma gibi duruyor
      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ı olarak sys/cdefs.h eklediğini ve burada yok sayılmaması gereken attribute'lar kullanılabileceğini hesaba katmıyor
    packed dışında aligned ve constructor da sık kullanılıyor
    Bunun bir issue tracker'da raporlanıp raporlanmadığını merak ediyorum. cdefs.h içindeki attribute kullanımlarının çoğu zaten __glibc_has_attribute ile 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 ediyorum
    libc başlıklarının kullandığı bazı özelliklerde sorun, derleyicinin bunları desteklediğini düzgün şekilde belirtmesinin bir yolunun olmaması. __has_attribute ya da __has_builtin gibi 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 #error veriyor. Ama desteklenmiyorsa sadece denemeye izin verip başarısız olmasından başka ne önerilebilir, bilmiyorum
    __builtin_va_list ile ilgili sorunlar da yaşadım. libc, __GNUC__ olmadan define va_list to void * yapabiliyor ya da hatta çakışan tanımlar koyabiliyor. Bu da __has_builtin ile 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/sys ve /usr/include/bits altı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 gerekir
      glibc 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_list oldukça ciddi. __has_builtin(__builtin_va_list) çalışır diye beklerdim ama görünüşe göre öyle değilmiş