Faragiri

همهٔ زبان‌ها

آموزش زبان سی سی، سی‌پلاس‌پلاس و سی‌شارپ

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

۴بخش
۳دقیقه خواندن
۴عمق

یک زبان کوچک

چیزی که استاندارد تضمین می‌کند و چیزی که نمی‌کند

بالای خط آب چیزهایی هستند که همیشه درست‌اند؛ زیر آن چیزهایی که به کامپایلر و سکو و پرچم ساخت بسته‌اند.

  • char یک بایت استتنها اندازه‌ای که استاندارد قطعی گفته
  • رشته با بایت صفر تمام می‌شودو طولش هیچ‌جا ذخیره نشده
چیزی که در کد می‌بینید
  • اندازه بقیه نوع‌هافقط حداقل دارد، و کد وابسته به چهار بایت روی سکوی دیگر می‌شکند
  • سرریز عدد علامت‌دارتعریف‌نشده، و بهینه‌ساز فرض می‌کند رخ نمی‌دهد
  • خواندن بیرون از مرز آرایههیچ بررسی‌ای نیست و ممکن است هیچ خطایی هم ندهد
  • متغیر مقداردهی‌نشدهمقدارش هرچیزی است که در آن خانه حافظه مانده بود

این نقشه درباره خطاهای منطقی چیزی نمی‌گوید. هر چهار مورد پایین می‌توانند در برنامه‌ای باشند که همه آزمون‌هایش را با موفقیت رد می‌کند.

این تابع یک سطر است و دو جواب متفاوت می‌دهد، بسته به اینکه با چه پرچمی ساخته شده باشد:

  • int roshd(int n) { return n + 1 > n; }

صدا زدنش با بزرگ‌ترین عدد صحیح علامت‌دار، ساخته‌شده با clang -O0 عدد ۰ برمی‌گرداند و ساخته‌شده با clang -O2 عدد ۱. اندازه‌گیری‌شده روی یک مک‌بوک با تراشه M1 Max و Apple clang 21.0.0 در تاریخ ۲۰۲۶-۰۹-۰۳.

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

تعریف‌نشده یعنی هیچ قولی نیست

این کلمه در سی معنای دقیقی دارد و با «نامشخص» یکی نیست. رفتار تعریف‌نشده یعنی استاندارد هیچ چیزی درباره برنامه شما نمی‌گوید، نه فقط درباره آن یک عبارت. برنامه می‌تواند جواب درست بدهد، جواب غلط بدهد، یا بخشی از کد را که به آن سطر مربوط بود کلا حذف کند.

چهار موردی که بیشتر از همه پیش می‌آیند: سرریز عدد صحیح علامت‌دار، خواندن از حافظه‌ای که آزاد شده، خواندن بیرون از مرز آرایه، و استفاده از متغیر مقداردهی‌نشده. هیچ‌کدام هنگام کامپایل خطا نمی‌دهند و هیچ‌کدام تضمین نمی‌کنند که هنگام اجرا هم خطا بدهند. همین است که سی را از زبان‌هایی که در همان لحظه می‌ایستند جدا می‌کند: ترجمه پیش از اجرا، یا خواندن سطر به سطر در حین کار.

سی چه چیزی را واقعا تضمین می‌کند

اندازه هیچ نوعی تضمین‌شده نیست جز اینکه char یک بایت است و بقیه ترتیب حداقلی دارند. اگر اندازه دقیق می‌خواهید، نوع‌های عرض ثابت را از کتابخانه استاندارد بگیرید و اندازه را حدس نزنید؛ کدی که فرض می‌کند یک عدد صحیح چهار بایت است، روی سکوی دیگر بی‌صدا عوض می‌شود.

و رشته در سی یعنی آرایه‌ای از بایت که با بایت صفر تمام می‌شود. طولش هیچ‌جا ذخیره نشده، پس گرفتن طول یعنی خواندن تا رسیدن به آن بایت. همین یک تصمیم طراحی، منشا بخش بزرگی از خرابی‌های امنیتی چهل سال گذشته است، و دلیلش هم این است که هیچ چیزی جلوی نوشتن بعد از انتهای بافر را نمی‌گیرد.

جایی که کار گیر می‌کند، آموزش با نسخه‌های خیلی قدیمی است

در بسیاری از درس‌های فارسی، سی هنوز با کامپایلرهای دهه نود و روی محیط‌های شبیه‌سازی‌شده آموزش داده می‌شود، با هدرهایی که استاندارد نیستند و توابعی که سال‌هاست منسوخ‌اند. نتیجه‌اش این است که کسی که در دانشگاه سی خوانده، کدی می‌نویسد که روی یک کامپایلر امروزی ده هشدار می‌دهد و همان هشدارها را هم ندیده. اولین کاری که ارزشش را دارد این است که هشدارها را روشن و به خطا تبدیل کنید؛ بیشتر چیزهایی که این صفحه درباره‌شان حرف زد، با همان پرچم پیش از اجرا گفته می‌شوند.

ابزار، و سالی که این حرف درست است

در سال ۲۰۲۶ روی یک مک به‌روز، clang همان Apple clang 21.0.0 است و رایگان همراه ابزارهای خط فرمان اکسکد می‌آید؛ سال‌ها پیش جای gcc را به‌عنوان کامپایلر سیستم گرفت. دو ابزاری که واقعا رفتار تعریف‌نشده را پیدا می‌کنند، ضدعفونی‌کننده آدرس و ضدعفونی‌کننده رفتار تعریف‌نشده‌اند: با یک پرچم هنگام ساخت روشن می‌شوند، برنامه را کندتر می‌کنند، و در لحظه وقوع خطا با پیام دقیق می‌ایستند. هر دو همراه خود کامپایلر می‌آیند و به شبکه نیازی ندارند.

پرسش بعدی این است که آن حافظه‌ای که آزاد شده و دوباره خوانده می‌شود دقیقا کجاست و چه کسی صاحبش است، که آدرس، تخصیص، و خطاهایی که می‌سازند است. و زبانی که همین کلاس خطاها را در زمان کامپایل رد می‌کند در ایمنی حافظه، بدون جمع‌کننده زباله است.

کنار این

بالای این