- सिर्फ ISO C मानक का पालन करने वाला कोड बहुत कम मिलता है, और वास्तविक C कोडबेस फीचर जोड़ने तथा compiler·library-विशिष्ट कमियों को बायपास करने के लिए non-standard extensions पर निर्भर रहते हैं
- उपयोगी C compiler को
<stdio.h>जैसे system headers से ही निपटना पड़ता है, लेकिन glibc__attribute__((packed)),#include_nextजैसे GNU extensions और धारणाओं के जरिए बाधा खड़ी करता है - SDL का byteswapping logic ISA macros मौजूद होने पर inline assembly चुन सकता है, इसलिए GCC·clang नहीं होने वाले compilers से भी GCC-शैली extensions की अपेक्षा की जा सकती है
- OpenBSD और Gnulib में
extern inlineका हैंडलिंग C99 और GCC semantics के अंतर, platform-विशिष्ट branching, और_FORTIFY_SOURCEशर्तों की वजह से inline semantics compatibility को जटिल बनाता है - छोटे C compilers को upstream patch, downstream patch, समर्पित guard हासिल करने, या GCC compatibility की नकल करने में से चुनना पड़ता है, और feature test macros का विस्तार अधिक बेहतर दिशा लगता है
glibc headers से बनने वाली पहली बाधा
- उपयोगी C compiler बनने के लिए system C library headers को preprocess और parse कर पाना ज़रूरी है, और अगर
<stdio.h>को ही process नहीं किया जा सके तो hello world तक पास करना मुश्किल हो जाता है - GNU/Linux environment में यह बाधा सीधे glibc तक जाती है
- glibc, sys/cdefs.h में — जिसे लगभग सभी libc headers अप्रत्यक्ष रूप से include करते हैं — compiler द्वारा predefined macros की जाँच करके तय करता है कि कौन-से extensions समर्थित हैं
- unsupported extensions को संबंधित definitions हटाकर संभाला जाता है, लेकिन यह compatibility logic भी व्यवहार में टूट सकता है
-
struct epoll_eventऔर__attribute__((packed))- Linux के
sys/epoll.hमें मौजूदstruct epoll_event, GNU__attribute__((packed))का उपयोग करने वाला packed struct है - यह attribute 64-bit पर struct layout बदल देता है, इसलिए इसे ignore करने पर ABI टूट जाता है
- सिर्फ compiler में
__attribute__((packed))implement होना काफी नहीं है sys/cdefs.hमें ऐसा code है जो GCC, clang, या tcc न होने पर__attribute__(xyz)को empty macro के रूप में define कर देता है- नतीजतन, दूसरे compilers packed attribute को support करते हुए भी glibc headers में उससे वंचित हो सकते हैं
- यह तर्क भी दिया जा सकता है कि
epollheader Linux-विशिष्ट है, इसलिए C standard portability के मानदंड को वैसा का वैसा लागू करना आसान नहीं है
- Linux के
-
limits.hऔर#include_nextstddef.h,stdint.h,limits.h,float.hजैसे कुछ C headers freestanding implementations के लिए भी आवश्यक होते हैं, इसलिए compiler को इन्हें प्रदान करना पड़ता है- POSIX, standard C constants के अलावा POSIX-विशिष्ट constants को भी
limits.hमें define करने की मांग करता है, इसलिए compiler केlimits.hके ऊपर platform-विशिष्टlimits.hकी जरूरत पड़ती है - glibc का
<limits.h>, GNU C न होने पर ANSIlimits.hvalues सीधे define करता है, और GCC environment में#include_next <limits.h>के जरिए compiler header लाता है - यह संरचना मानती है कि GCC-विशिष्ट builtin
limits.hकुछ खास macros define करेगा, और#include_nextextension पर भी निर्भर करती है - clang को भी इस संरचना को बायपास करना पड़ता है
SDL की feature detection और inline assembly समस्या
SDL_endian.hमें byteswapping functions, जहाँ संभव हो, compiler builtins या inline assembly का उपयोग करती हैं, और अंतिम विकल्प के रूप में सामान्य bit operations implementation पर लौटती हैं- detection logic मोटे तौर पर इस क्रम में काम करता है
- अगर GCC या clang है और
__has_builtin(__builtin_bswapX)उपलब्ध है, तो builtin उपयोग किया जाता है - अगर MSVC 8.0 या उससे ऊपर है, तो MSVC intrinsic
#pragmaउपयोग होता है - अगर
__x86_64__जैसे ISA-विशिष्ट macros defined हैं, तो inline assembly उपयोग होती है - अन्यथा सामान्य bit operations implementation उपयोग होती है
- अगर GCC या clang है और
- GCC या clang नहीं होने वाला compiler यदि उचित कारणों से ISA-विशिष्ट predefined macro define करता है, तो यह क्रम समस्या पैदा करता है
- ऐसा compiler भले ही bswap builtin और
__has_builtinspecial operator प्रदान करे, logic के कारण वह GCC-शैली inline assembly की ओर जा सकता है - परिणामस्वरूप संरचना यह मानकर चलती है कि अज्ञात compiler भी GCC-style inline assembly support करेगा
OpenBSD libc और extern inline की उलझन
- OpenBSD के कुछ headers optimization के दौरान compiler द्वारा वैकल्पिक रूप से उपयोग की जाने वाली inline function definitions शामिल करते हैं
- ये functions
__only_inlinemacro से define होती हैं, और अगर compiler वास्तव में inline नहीं करता तो उन्हें external symbol से replace होना चाहिए - यानी extern linkage वाली inline function की जरूरत होती है
-
C99 inline और GCC inline semantics का अंतर
inline, C99 में निर्दिष्ट है, लेकिन standard behavior, C99 से पहले के non-standard GCC behavior से टकराता है- header के अंदर inline definition को function body के साथ
extern inlineका उपयोग करना चाहिए, और इस स्थिति में वास्तविक exported function emit नहीं होती - translation unit में function definition export करने के लिए declaration पर सिर्फ
inlineलगाना चाहिए inlineका अर्थ C++ और C में भी अलग होता है- इस अंतर पर Youtao Guo के लेख में विस्तार से चर्चा है
-
OpenBSD का
__only_inline- OpenBSD, GCC inline semantics पर निर्भर करता है
- GCC versions के अंतर को ढकने के लिए sys/cdefs.h का
__only_inlinemacro, नए GCC पर पुराने gnu89 inline semantics को स्पष्ट__attribute__के जरिए निर्दिष्ट करता है - non-GNU compilers में
__only_inline,staticlinkage के रूप में define होता है - परिणामस्वरूप functions परस्पर विरोधी linkage के साथ declare/define होकर टूट सकती हैं
-
_ANSI_LIBRARYworkaround- OpenBSD,
_ANSI_LIBRARYmacro का सम्मान करता है - इस macro को define करने पर
signal.hजैसे standard headers में टूटने वाली__only_inlinedefinitions का उपयोग पूरी तरह छोड़ दिया जाता है - optimized version नहीं मिलता, लेकिन कम-से-कम build काम करता है
- OpenBSD,
-
Gnulib का
extern inlinecompatibility code- Gnulib का
extern inlinecompatibility code, Guile और nano build करते समय भी सामने आता है - extern-inline.m4 इस C corner case की टूटी और विचित्र implementations को संभालने के लिए जटिल conditional branching शामिल करता है
- इन शर्तों में Apple, DragonFly, FreeBSD, GCC, clang, PCC, HP cc, PGI, SunPro C,
_FORTIFY_SOURCE,__GNUC_STDC_INLINE__,__GNUC_GNU_INLINE__जैसे environment differences झलकते हैं
- Gnulib का
Android bionic की clang-आधारित धारणाएँ
- bionic Android की libc है, और उसके headers, GCC की तुलना में clang को कहीं अधिक मजबूती से assume करते हैं
- bionic headers, nullability checks के लिए
_Nonnull,_Null_unspecifiedजैसे clang-विशिष्ट extensions का खूब उपयोग करते हैं - ऐसे macros को command-line flags से
#defineकरके हटाना बहुत मुश्किल नहीं है - Android फोन को Termux के जरिए native aarch64 development environment की तरह इस्तेमाल करते समय bionic headers में यह समस्या सामने आती है
_Null_unspecifiedको__BIONIC_COMPLICATED_NULLNESSभी कहा जाता है, और संबंधित definition bionic के sys/cdefs.h में है
छोटे C compilers के सामने आने वाले विकल्प
- सिर्फ ISO C मानक का पालन करने वाला कोड वास्तविक दुनिया में दुर्लभ है, और कई C codebases non-standard behavior और language extensions पर निर्भर रहते हैं
- यह निर्भरता सिर्फ अतिरिक्त features के लिए नहीं, बल्कि compiler और library के अनुसार अलग-अलग bugs और gaps को बायपास करने की प्रक्रिया में भी पैदा होती है
- कई environments को support करने वाले codebases preprocessor checks और guards पर निर्भर रहते हैं, लेकिन यह तरीका आसानी से टूटता है और संभालना कठिन होता है
- antcc जैसे C compiler बनाते समय ऐसे compatibility issues बार-बार सामने आते हैं
- जब कई open source projects गैर-जरूरी कामों के लिए भी compiler-विशिष्ट non-standard extensions और behaviors पर निर्भर करते हैं, तो वैकल्पिक compilers पर बोझ बढ़ जाता है
- साथ ही यह मांग करना भी कठिन है कि हर developer छोटे और कम-ज्ञात compilers समेत कई compilers पर C code test करे
- C portability अपने आप में ही काफी कठिन है
- compiler लेखक के नज़रिए से चार संभावित विकल्प हैं
- incompatibility के लिए upstream में patch करने की कोशिश करना
- इतना प्रसिद्ध हो जाना कि developers समर्पित
#ifdefchecks और basic tests जोड़ें - downstream में इसे संभालना, और patches या अलग patches वितरित करना
- किसी खास GCC version होने का ढोंग करना और उन extensions को implement करना
- upstream patches एक कठिन लड़ाई जैसी लगती हैं, और downstream patches सबसे आसान तरीका हैं
- बहुत-से codebases को users और developers के लिए न्यूनतम भ्रम के साथ support करना हो, तो GCC compatibility की नकल व्यावहारिक लगती है, लेकिन इसकी implementation cost बड़ी है
- clang, GCC 4.2.1 compatibility का दावा करने के लिए
__GNUC__=4,__GNUC_MINOR__=2,__GNUC_PATCHLEVEL__=1define करता है - आज clang लगभग अलग support target जैसा है, लेकिन Linux kernel को clang से compile करने लायक बनाने के लिए दोनों projects में patching की बड़ी मेहनत लगी थी
GCC macros और catch-up की समस्या
- GCC होने का ढोंग करने वाली पद्धति में भी समस्या है
- कई codebases सिर्फ
#ifdef __GNUC__जाँचते हैं और version check के बिना नए GCC extensions इस्तेमाल कर सकते हैं - इस स्थिति में वैकल्पिक compiler को लगातार catch-up करना पड़ता है
- clang, 4.2.1 से नए GNU extensions support करने के बावजूद
__GNUC__macro value न बढ़ाने की एक वजह यही है - संबंधित पृष्ठभूमि LLVM की
__GNUC__minor version बढ़ाने पर चर्चा में मिलती है
बेहतर दिशा और वर्तमान स्थिति
- आदर्श रूप से compiler-विशिष्ट guards और version checks के बजाय feature test macros का अधिक व्यापक उपयोग होना चाहिए
- उपयोगी feature test macros में
__has_builtin,__has_feature,__has_attributeशामिल हैं __STDC_NO_VLA__जैसे standard macros की शैली भी अधिक उपयोग की जा सकती है- मौजूदा *NIX दुनिया में, अच्छा हो या बुरा, GCC/clang quasi-duopoly डिफ़ॉल्ट स्थिति है
- इसके बावजूद स्वतंत्र छोटे C compilers का development जारी है
1 टिप्पणियां
Lobste.rs की राय
(kefir compiler के लेखक) अनुभव के अनुसार
<sys/cdefs.h>की__attribute__समस्या सबसे सिरदर्द वाली चीज़ों में से एक है। इसने epoll, आम packed struct, constructor और symbol visibility को तोड़ दिया, इसलिए मुझे kefir के साथ यह monkeypatch header भी शामिल करना पड़ायह आदर्श नहीं है, लेकिन शायद सबसे व्यावहारिक तरीका है, और वास्तव में इससे external test suite में ज़्यादातर custom patch हटाए जा सके
विफलता का एक और प्रकार buggy alternative code है। कुछ प्रोजेक्ट compiler को पहचानकर उसी हिसाब से काम करने की कोशिश करते हैं, लेकिन alternative compiler पर testing कम होने की वजह से fallback code bug से भरा होता है या ठीक से maintain नहीं किया जाता। compiler लेखक के नज़रिए से यह सीधे “unsupported compiler” कहकर fail होने से कहीं ज़्यादा परेशान करने वाला है। उदाहरण के लिए, program और precompiled library के बीच integer typedef width mismatch जैसी अजीब miscompile को खुद debug करना पड़ता है
$TERMकोxterm-256colorपर सेट करके xterm होने का नाटक न करो, तो हर तरह की चीज़ें टूट जाती हैंइसे कैसे सुलझाया जाए, सच में समझ नहीं आता। आख़िरकार क्या हमारे प्रोजेक्ट को इतना व्यापक और मशहूर होना पड़ेगा? आसान है!
compiler-detection fallback को ठीक से maintain न करने से होने वाले अजीब miscompile मैं भी कुछ बार झेल चुका हूँ, और यह सच में बहुत झुंझलाने वाला है
मैं मुख्य रूप से linux-musl पर cproc विकसित करता हूँ, इसलिए यह नहीं पता था कि glibc दूसरे compiler पर
__attribute__को disable कर देता है, लेकिन यह वाकई काफ़ी बुरी स्थिति है। comment में लिखा है कि attribute usage को ignore कर दिया जाए तो भी ठीक है, लेकिन इसमें यह ध्यान नहीं रखा गया कि ज़्यादातर application code अप्रत्यक्ष रूप सेsys/cdefs.hको include करता है और ऐसे attribute इस्तेमाल कर सकता है जिन्हें ignore नहीं किया जाना चाहिएpackedके अलावाalignedऔरconstructorभी आम तौर पर इस्तेमाल होते हैंयह कहीं issue tracker में report हुआ है या नहीं, जानने की उत्सुकता है। लगता है
cdefs.hमें attribute का ज़्यादातर इस्तेमाल पहले से__glibc_has_attributeसे guard किया गया है, इसलिए यह भी जानना चाहता हूँ कि__attribute__को एकदम disable कर देने से वास्तव में हासिल क्या होता है, और क्या इसे हटाया जा सकता हैlibc header जिन features का इस्तेमाल करते हैं, उनमें कुछ ऐसे भी हैं जिनके लिए compiler के पास support को ठीक से indicate करने का तरीका नहीं होता। वे
__has_attributeया__has_builtinजैसी विधि से सामने नहीं आते; जो उदाहरण तुरंत याद आता है वह__asm__label है। NetBSD इसे symbol name बदलने के लिए इस्तेमाल करता है, और अगर__GNUC__या__PCC__न हो तो#errorदेता है। लेकिन support न होने पर बस कोशिश करने और fail होने देने के अलावा क्या सुझाया जाए, यह समझ नहीं आता__builtin_va_listसे जुड़ी समस्या भी झेली है। libc,__GNUC__के बिना defineva_listtovoid *कर देता है, या कभी-कभी तो conflicting definition भी रखता है। इसे भी__has_builtinसे test नहीं किया जा सकता।__has_builtin(__builtin_va_arg)शायद काफ़ी अच्छा test हो सकता है, लेकिन macOS में इसे कैसे ठीक करवाया जाए, यह नहीं पता/usr/include/sysऔर/usr/include/bitsमें__attribute__usage को जल्दी से खोजने पर बहुत-से unguarded इस्तेमाल मिले। ज़्यादातर__format__,__aligned__,__noreturn__थे, इसलिए इन्हें भी ठीक करना होगाकुल मिलाकर glibc शायद GCC के अलावा दूसरे compiler के साथ compatibility को प्राथमिकता नहीं देता, इसलिए ऐसे patch स्वीकार किए जाएँगे या नहीं, कहना मुश्किल है। इस साल की शुरुआत में system upgrade के बाद glibc ने Linux header में unguarded
__SIZE_TYPE__usage जोड़ दिया, जिससे मेरा compiler कुछ प्रोजेक्ट compile नहीं कर पा रहा था। मैंने report किया, लेकिन अभी तक ठीक नहीं हुआ, और आखिरकार GCC के अनुरूप होने के लिए मुझे__X_TYPE__शैली के predefined macro जोड़ने पड़े__asm__label वाली समस्या का कोई अच्छा हल सूझता नहीं। लेकिन अगर asm name change सच में काम करने के लिए 100% ज़रूरी है, तो compiler check करने से बेहतर शायद यह होगा कि बस कोशिश की जाए और fail होने दिया जाए__builtin_va_listकाफ़ी गंभीर मामला है। मुझे लगा था कि__has_builtin(__builtin_va_list)काम करेगा, लेकिन apparently ऐसा नहीं है