یک کارگاه در تبریز فریمور یک کنتور آب هوشمند را مینویسد و به دکمهای نیاز دارد که عدد نمایشگر را صفر کند. در C نسخهای که به ذهن میرسد هیچ کاری نمیکند:
void tare_be_meghdar(double vazn) { vazn = 0.0; }مقدار فراخوان را روی ۲٫۴۸۰ باقی میگذاردvoid tare(double *vazn) { *vazn = 0.0; }که باtare(&vazn)صدا زده شود آن را ۰٫۰۰۰ میکند
تابعی که double vazn میگیرد نمیتواند متغیر فراخوان را عوض کند، چون یک رونوشت گرفته است. تابعی که double *vazn میگیرد آن را از راه اشارهگر عوض میکند، با *vazn = 0.0. همین تمایز کل این خانواده است: &vazn نشانی است و *vazn مقداری که آن نشانی نگه داشته، و هر تفاوتی میان این سه زبان و زبانهای بالاتر از همینجا شروع میشود.
اندازهها هم دانستنشان با شماست. sizeof(double) برابر ۸ بایت است و sizeof در زمان کامپایل جواب میگیرد، نه موقع اجرا. وقتی نام مشترک روی دستگاه ذخیره میشود، بایتها را خودتان میخواهید: malloc(8) شش حرف hamour را بهعلاوه صفر پایانیاش جا داد و یک بایت هم اضافه آمد. هفت بخواهید باز هم جا میشود. شش بخواهید و امروز هیچ چیز اعتراض نمیکند.
وقتی دفترداری اشتباه باشد چه اتفاقی میافتد
این بخشی است که دیدنش میارزد نه شنیدنش. برنامهای که آن نام را آزاد میکند و بعد چاپش میکند، کامپایلشده بدون هیچ پرچمی و اجراشده روی همین ماشین در سوم سپتامبر ۲۰۲۶: خواندن حافظه آزادشده چیزی چاپ نکرد و با کد صفر خارج شد، بدون هیچ خطایی. یک کروشه خالی، یک خروج موفق، و برنامهای که اشتباه است به شکلی که هیچ چیز روی آن ماشین اسمش را نبرد.
همان برنامه با یک پرچم اضافه کامپایل شود و میایستد و میگوید چه شد. clang -fsanitize=address آن را heap-use-after-free نامید، روی همان سطری که این کار را کرده بود، با نام فایل و شماره سطر، در حدود یک ثانیه. آن پرچم تفاوت میان دو هفته مزخرف بریدهبریده و یک بعدازظهر است، و مفیدترین چیز این صفحه است. نشانی و تخصیص و خطاهایی که میسازند نشانی، تخصیص، و خطاهایی که میسازند هستند.
سه زبان، و شباهت خانوادگی فقط نقطهگذاری است
آکولاد و سمیکالن مشترکاند و تقریباً هیچ چیز دیگری. C کوچک است و همین کوچکی ویژگیاش است: عملاً زمان اجرایی ندارد، پس جایی میرود که هیچ چیز دیگری جا نمیشود، که نزدیک به ماشین، و هیچ چیز پنهان است. در ایران هم بیشترین جایی که C واقعاً نوشته میشود همینجاست: برد صنعتی، کنتور، تابلوی روان و هر چیزی که حافظهاش با کیلوبایت اندازه گرفته میشود.
C++ همه آن را نگه میدارد و راهی برای بیرون آمدن از دفترداری اضافه میکند. C++ آزاد کردن را به پایان یک دامنه گره میزند، پس آزادسازی سطری نیست که شما بنویسید: شیای که حافظهای را در اختیار دارد، وقتی از دامنه بیرون میرود آزادش میکند، چه تابع عادی برگشته باشد چه استثنا داده باشد. بهایش این است که زبان چهل سال افزوده در خودش دارد و هیچ دو کد یکسانی از یکسوم مشترک آن استفاده نمیکنند، که C بهعلاوه شی و قالب و چهل سال افزوده است.
C# راه دیگری رفت. C# زباله جمع میکند، پس آزادسازی نه نوشته میشود و نه فراموش. همان خواندن بالا در آن یک خطای زبانی میشد نه یک سطر خالی. این یکی از آن سه، به همان اندازه که زبان است سکو هم هست، و آنچه آن سکو به آن میدهد جواب مایکروسافت، و آنچه داتنت به آن میدهد است. برای ایران یک نکته عملی دارد: بیشتر کار C# اینجا یا نرمافزار اداری ویندوزی است یا بازی داخل Unity، و آن دو دنیا تقریباً هیچ کتابخانه مشترکی ندارند.
معامله
C چه چیزی به شما میدهد، و بعد چه چیزی از شما میخواهد
تراز رسم شده چون معامله واقعی است نه اشتباه. دو کفه هموزناند و اینکه کدام سمتش را میخواهید کاملاً به این بستگی دارد که کد کجا باید اجرا شود.
- آنچه به شما داده میشودهیچ چیز میان متن و دستور ماشین نمیایستد
- نشانی واقعی یک مقدار
- چیدمانی در حافظه که خودتان انتخاب کردهاید
- هیچ جمعکنندهای چیزی را متوقف نمیکند
- فایل اجراییای که به نصب چیزی نیاز ندارد
- آنچه از این به بعد بدهکاریدهیچکدام از اینها موقع اجرای برنامه بررسی نمیشود
- این حافظه مال کیست
- چقدر زندگی میکند، و چه کسی آزادش میکند
- طول آرایه، که هیچ چیز ثبتش نکرده
- اشتباه بودن بیصدا، و خروج با کد صفر
هیچ زمان اجرایی در میانه نیست
ابزار، و سالی که این حرف درست است
Clang کامپایلری است که باید رویش یاد گرفت و رایگان است. جای GCC را بهعنوان پیشفرض روی مک و BSDها گرفت و دلیلش پیامهایش است: پیامی که اسم اشتباه را میآورد و ستون را نشان میدهد، بهجای سبک قدیمیتر که سطر را میگفت و بقیهاش را به شما میسپرد. GCC روی لینوکس هنوز پیشفرض است و آن دو آنقدر نزدیکاند که کد بینشان جابهجا میشود.
سنجشگرها که داخل Clang هستند و رایگاناند، همانهاییاند که این کار را عوض کردند. AddressSanitizer جای Valgrind را برای کار روزمره گرفت، و تفاوتش این است که حدود دو برابر زمان اجرا خرج برمیدارد نه بیست برابر، یعنی میشود در آزمون عادی روشن باشد بهجای اینکه برای یک بعدازظهر بد نگه داشته شود.
CMake سامانه ساختی است که بیشتر پروژههای C و C++ از آن استفاده میکنند و رایگان است. جای makefileهای دستنویس برای هر سکو را گرفت. هیچکس از CMake لذت نمیبرد، و هیچ جایگزینی بهاندازه آن برای پروژههای C قابل فهم نیست.
هر سه بدون حساب و بدون پرداخت نصب میشوند، و این در خانواده C برخلاف بیشتر جاهای دیگر این پیلر یک آسودگی واقعی است: کامپایلر و سنجشگر و سامانه ساخت، هیچکدام مخزن بستهای نمیخواهند که باید دورش زد.
اشتباه: C را چون کوچک به نظر میرسد زبان اول گرفتن
زبان واقعاً کوچک است و همین گمراه میکند. نحوش در چند صفحه جا میشود، پس مثل موضوعی کوتاه خوانده میشود، و بعد معلوم میشود موضوع اصلی همان دفترداریای است که هیچ زبان دیگری از شما نمیخواهد: این حافظه مال کیست، چقدر زندگی میکند، چه کسی آزادش میکند، و طول آن آرایه چقدر است، چون هیچ چیز در زمان اجرا نمیداند.
تله مشخصش این است که اشتباه بودن، بیصداست، و آن سطر خالی بالا اثباتش است. هر زبان دیگری روی این سایت وقتی اشتباه میکنید به شما میگوید. اینجا برنامه اجرا میشود، موفق خارج میشود، روی کنتور آب عدد کمی اشتباه میسازد، و ماهها همین کار را میکند. پس قاعده برای هرکسی که این را یاد میگیرد: سنجشگر را از اولین برنامه روشن کنید، نه از اولین اشکال، و روشن بگذاریدش.
پرسش باز این است که آیا این دفترداری را میشود بهجای به یاد سپردن، بررسی کرد، و امروز جواب جدیای برایش هست که زیر زبانهای برنامهنویسی نشسته است. جایی که این معامله هنوز واقعاً به نفع این خانواده است هر جایی است که بودجه ثابتی برای هر فریم دارد، که بازیسازی است، و چیزی که اشارهگر واقعاً به آن اشاره میکند کد چطور اجرا میشود است.