D dverlaeckt80 در sebsite.pw

شکاف میان استاندارد مکتوب C و کامپایلرهای واقعی: چرا کد شما واقعاً Standard C نیست؟

۴ دقیقه مطالعه ۱۳۴ امتیاز ۱۱۸ نظر ۲۳ مهر ۱۴۰۵

شکاف میان استاندارد مکتوب C و کامپایلرهای واقعی: چرا کد شما واقعاً Standard C نیست؟ — Your C is not standard C

خلاصه

بسیاری از برنامه‌نویسان تصور می‌کنند کامپایلرهایی مانند GCC و Clang متن استاندارد زبان C را مو‌به‌مو پیاده‌سازی کرده‌اند. با این حال، تفاوت‌های فنی عمیقی در نحوه مدیریت کلمات کلیدی، ماکروهای اعشاری، پرگماها و حتی هدرهای کتابخانه‌هایی مانند musl وجود دارد که نشان می‌دهد C در دنیای واقعی با استاندارد ISO تفاوت دارد.

در میان زبان‌های برنامه‌نویسی سیستمی، C همواره به عنوان زبانی شناخته می‌شود که استانداردهای مکتوب و سخت‌گیرانه‌ای از سوی ISO برای آن تدوین شده است (از C89 و C99 گرفته تا نسخه تازه C23). اما در عمل، توسعه‌دهندگانی که روی اکوسیستم‌های رایج متکی به GCC، Clang، glibc یا musl کار می‌کنند، زبانی را می‌نویسند و کامپایل می‌کنند که در موارد متعددی مستقیماً مشخصات استاندارد را نقض می‌کند یا نادیده می‌گیرد.

بررسی ناسازگاری‌های رفتاری این ابزارها با استانداردهای رسمی C نکات فنی جالب و گاه دردسرسازی را برای توسعه‌دهندگان کدهای پرتابل آشکار می‌کند.

کلیدواژه asm و نقض شناسه در حالت پیش‌فرض

به‌صورت پیش‌فرض، کامپایلرهای GCC و Clang واژه asm را به عنوان یک توسعه اختصاصی (extension) شناسایی کرده و آن را کلمه کلیدی در نظر می‌گیرند. طبق استاندارد C، کلمه asm بر خلاف C++ یک شناسه رزرو شده نیست و یک برنامه منطبق بر استاندارد اجازه دارد از آن به عنوان نام متغیر یا تابع استفاده کند. اگرچه با پرچم‌های سازگاری مانند std- می‌توان رفتار استاندارد را اعمال کرد، اما واقعیت این است که این کامپایلرها در پیکربندی پیش‌فرض خود کاملاً منطبق بر استاندارد عمل نمی‌کنند.

ماکروی FLT_ROUNDS و معضل مدهای گرد کردن

استاندارد C در فایل float.h ماکرویی با نام FLT_ROUNDS تعریف کرده است که باید شیوه گرد کردن محاسبات اعشاری (Floating-Point) را در زمان اجرا منعکس کند؛ یعنی اگر تابعی مثل fesetround از هدر <fenv.h> جهت تغییر مد گرد کردن فراخوانی شود، مقدار FLT_ROUNDS باید به شکل پویا این تغییر را نشان دهد.

با این حال، GCC این مقدار را همواره برابر با 1 (گرد کردن به نزدیک‌ترین عدد) هاردکد کرده و هیچ توجهی به تغییرات مد اعشاری در زمان اجرا ندارد. کامپایلر Clang برای این کار یک تابع درونی تحت عنوان ()builtin_flt_rounds__ ارائه داده، اما GCC همچنان این باگ تاریخی را در خود حفظ کرده است.

پرگماهای استاندارد مفقود بعد از ربع قرن

استاندارد C99 چندین دستور پیش‌پردازنده (Pragma) اختصاصی برای کنترل دقیق محاسبات اعشاری معرفی کرد؛ مانند:

  • #pragma STDC FP_CONTRACT
  • #pragma STDC FENV_ACCESS
  • #pragma STDC CX_LIMITED_RANGE

نکته عجیب اینجاست که با گذشت بیش از ۲۵ سال از انتشار C99، کامپایلر GCC هنوز هیچ‌یک از این پرگماها را پیاده‌سازی نکرده و با آن‌ها شبیه به کد بی‌اثر (No-op) رفتار می‌کند. اگرچه برخی از این رفتارها با فلگ‌های کامپایلر قابل شبیه‌سازی هستند، اما تفاوت بزرگ پرگما با فلگ در این است که پرگما می‌تواند به صورت محلی و فقط روی یک بلوک یا تابع خاص اعمال شود، در حالی که فلگ روی کل واحد کامپایل (Translation Unit) تأثیر می‌گذارد. Clang پرگماهای C99 را تا حد زیادی پشتیبانی می‌کند، اما هنوز پرگماهای مربوط به C23 را پیاده نکرده است.

ناسازگاری بازگشت تابع و تفکیک انواع در Clang

طبق متن استاندارد، استفاده از توصیف‌کننده‌ها (Qualifiers) مثل const در نوع بازگشتی توابع بی‌اثر بوده و باید نادیده گرفته شوند. به عنوان نمونه، اعلان ()int f و ()const int f باید معادل یکدیگر باشند. اما اگر این دو اعلان را به Clang بدهید، با خطای مغایرت در تایپ (Conflicting types) مواجه می‌شوید؛ زیرا Clang این توصیف‌کننده را به اشتباه در تعریف نوع تابع تأثیرگذار می‌داند.

مشکل musl در هدر stdarg.h و استاندارد C23

کتابخانه کم‌حجم musl برخی هدرهای استاندارد نظیر stdarg.h را خودش عرضه می‌کند، در حالی که این هدرها معمولاً مستقیماً توسط خود کامپایلر مدیریت می‌شوند. هدر اختصاصی musl ماکروی va_start را به شکل دوآرگومانی تعریف کرده است (__builtin_va_start(v, l)). اما در استاندارد C23، نیاز به آرگومان دوم در va_start برداشته شده است. به دلیل بازنویسی این هدر توسط musl، حتی اگر کامپایلر شما از C23 پشتیبانی کند، حین استفاده از musl امکان کامپایل فرم استاندارد و مدرن این ماکرو وجود نخواهد داشت.

چرا این تفاوت‌ها برای مهندسان اهمیت دارد؟

بحث‌های شکل‌گرفته در جامعه توسعه‌دهندگان Hacker News نشان می‌دهد که بسیاری از مهندسان واقع‌گرا، استانداردسازی مدرن C را تا حدی جدا از نیازهای کاربردی دنیای واقعی می‌دانند. برخی معتقدند اضافه شدن ویژگی‌های جدید و دست‌نخورده ماندن باگ‌های قدیمی در کامپایلرهای غول‌پیکری چون GCC و Clang، نوشتن کد کاملاً پرتابل را دشوار کرده است. به عنوان مهندس نرم‌افزار، نباید تنها به متن سند ISO اتکا کرد؛ شناخت این شکاف‌ها به شما کمک می‌کند هنگام دیباگ رفتارهای عجیب اعشاری، خطاهای تایپ کامپایلر و پورت کردن کدها میان سیستم‌عامل‌ها و کتابخانه‌های مختلف، وقت کمتری را صرف حدس و گمان کنید.

این مطلب به‌صورت کاملاً خودکار و با هوش مصنوعی از مقالهٔ اصلی و بحث هکرنیوز تهیه شده و ممکن است خطا داشته باشد. حقوق مطلب اصلی متعلق به نویسندهٔ آن است. قوانین و گزارش مشکل

این خبر را با دیگران به اشتراک بگذارید

خبرهای فناوری در قالب پست و ویدیوی کوتاه، در اینستاگرام هکرنیوز بلاگ. به‌زودی
هر خبر به محض انتشار، به‌همراه پادکست روزانه در کانال تلگرام هکرنیوز بلاگ.