کامپایلرهای جایگزین C و کابوس هدرهای غیراستاندارد
خلاصهٔ کاملتر
هر کسی که C نوشته باشه میدونه که کد کاملاً منطبق با استاندارد ISO C تقریباً وجود نداره. اکثر کدهای دنیای واقعی به رفتارهای غیراستاندارد و اکستنشنهای زبانی تکیه دارن — نه الزاماً برای قابلیتهای اضافه، بلکه برای دور زدن باگها و خلأهای کامپایلرها و کتابخانههای مختلف. نویسندهی این مقاله در حال توسعهی کامپایلر C مستقل خودشه و این موارد رو از نزدیک تجربه کرده.
glibc اولین مانع جدیه. فایل sys/cdefs.h که بهطور غیرمستقیم توسط هر هدر libc اینکلود میشه، پر از چکهای پیشپردازنده برای ماکروهای اختصاصی کامپایلره. برای مثال، اگه کامپایلر شما نه GCC باشه، نه Clang، و نه TCC، این کد attribute رو بهکلی نادیده میگیره — حتی اگه کامپایلرتون واقعاً این اتریبیوت رو پشتیبانی کنه:
#if !(defined __GNUC__ || defined __clang__ || defined __TINYC__)
# define __attribute__(xyz) /* Ignore */
#endifاین یعنی ساختارهایی مثل struct epoll_event که با attribute((packed)) تعریف شدن و تغییر چیدمان حافظه میدن، روی کامپایلرهای ناشناس خراب میشن.
SDL هم داستان مشابهی داره. منطق تشخیص قابلیت در SDL_endian.h برای انتخاب بهترین روش بایتسوئپینگ اینطوریه: اول GCC/Clang رو چک میکنه، بعد MSVC، بعد اسمبلی اینلاین ISA-specific. این یعنی اگه کامپایلر شما GCC نیست ولی ماکروی معماری مثل x86_64 رو تعریف کرده، SDL سعی میکنه از اسمبلی اینلاین استایل GCC استفاده کنه — حتی اگه شما __has_builtin رو هم پیادهسازی کرده باشین.
OpenBSD libc با ماکروی __only_inline برای تعریف توابع inline مشکلساز دیگهای داره. روی کامپایلرهای غیر-GNU این ماکرو به static تبدیل میشه که با لینکیج خارجی تابع تضاد داره. خوشبختانه یه ماکروی فرار هم هست: اگه _ANSI_LIBRARY رو تعریف کنین، این تعریفهای شکسته کنار میرن. bionic، یعنی libc اندروید، هم در طرف مقابل قرار داره — بهجای GCC، همهچیز رو برای Clang فرض گرفته و پر از اکستنشنهای اختصاصی Clang مثل _Nonnull و _Null_unspecifiedه.
در نهایت نویسنده چهار راهحل ممکن برای توسعهدهندگان کامپایلر مطرح میکنه: پچ کردن ناسازگاریها در upstream، کسب محبوبیت کافی تا دیگران برات تست اضافه کنن، توزیع پچهای خودت، یا تظاهر به GCC بودن و پیادهسازی اکستنشنهاش. گزینهی آخر واقعبینانهترینه — همون کاری که Clang انجام میده و ماکروی GNUC=4 رو تعریف میکنه تا سازگاری با GCC 4.2.1 رو ادعا کنه.
ایدهآل اینه که از ماکروهای تشخیص قابلیت مثل __has_builtin و __has_feature و __has_attribute بیشتر استفاده بشه تا از چکهای compiler-specific. ولی فعلاً دوگانهی GCC/Clang وضع موجود دنیای *NIXه و کامپایلرهای مستقل کوچیکتر مثل TCC، kefir، cproc و بقیه باید با این واقعیت کنار بیان.
نکات کلیدی:
- هدرهای glibc با چک GNUC || clang || TINYC عملاً بقیهی کامپایلرها رو بلاک میکنن
- SDL برای بایتسوئپینگ به اسمبلی اینلاین GCC فالبک میکنه حتی وقتی کامپایلر __has_builtin داره
- OpenBSD libc با ماکروی _ANSI_LIBRARY یه راه فرار برای کامپایلرهای غیر-GNU داره
- bionic اندروید برخلاف بقیه، به Clang وابستهست نه GCC
- واقعبینانهترین راه برای کامپایلرهای جدید: تظاهر به GCC بودن، درست مثل کاری که Clang میکنه




