دستاورد محوری
دنیس ریچی در Bell Labs زبان C را توسعه داد و همراه کن تامپسون در ساخت Unix نقش اساسی داشت. C امکان بیان برنامههای سیستمی را با سطحی بالاتر از اسمبلی فراهم کرد، در حالی که کنترل نزدیک به ماشین حفظ میشد.
پرسش ماندگار
طراحی ابزار سیستمی همواره میان کنترل، قابلیت حمل و پیچیدگی مصالحه میکند. مرور این مسیر برای فهم میراث ابزارهای امروز مفید است، نه برای نادیدهگرفتن مخاطرات ایمنی حافظه.
چرا زبان C در تاریخ سیستمها مهم است؟
یک زبان سیستمی باید بتواند ساختار داده، جریان اجرای برنامه و ارتباط با حافظه را بیان کند. نزدیکبودن به ماشین کنترل بیشتری میدهد، اما جزئیات بیشتری را نیز بر دوش برنامهنویس میگذارد. C در این میان راهی برای نوشتن بخشهای مهم نرمافزار سیستمی با بیان سطح بالاتر از اسمبلی فراهم کرد؛ زمینهای که پیوند آن با توسعهٔ Unix را قابل فهم میکند.
قابلیت حمل را نباید به معنی اجرای بدون تغییر روی هر محیط دانست. اندازهٔ دادهها، قرارداد فراخوانی، ترتیب بایتها و رابط سیستمعامل میتوانند میان محیطها متفاوت باشند. یک طراحی قابل حمل این تفاوتها را شناسایی و در مرزهای مشخص محصور میکند، بهجای آنکه آنها را در سراسر کد پخش کند.
کنترل حافظه؛ قدرت همراه با مسئولیت
وقتی برنامهنویس طول یک بافر یا عمر یک شیء را مدیریت میکند، باید بداند کدام بخش حق خواندن، نوشتن یا آزادکردن آن را دارد. اشتباه در همین قراردادهای کوچک میتواند به خرابی، افشای داده یا رفتار ناخواسته منجر شود. این توضیح، نقد شخصی یک طراح نیست؛ بخشی از فهم مصالحههای ابزارهای سیستمی است.
برای نمونه، تابعی که دادهٔ ورودی را در بافر مقصد مینویسد باید ظرفیت مقصد را بداند. صرف اینکه ورودی «معمولاً کوتاه» است تضمین فنی محسوب نمیشود. بررسی مرز، آزمون ورودی خالی و بیشازحد بزرگ، و مشخصکردن مالکیت حافظه، از عادتهای مهم بازبینی چنین کدیاند.
برداشت آموزشی IROSG
برای مطالعهٔ این میراث، یک برنامهٔ کوچک را از کد منبع تا فایل اجرایی دنبال کن: پیشپردازش چه میکند، کامپایلر چه میسازد و پیونددهنده چگونه وابستگیها را کنار هم قرار میدهد؟ سپس همان پروژه را روی دو محیط آزمایش کن و تفاوتها را ثبت کن. هدف صرفاً یادگرفتن دستور ساخت نیست، بلکه فهم قرارداد میان زبان، ابزار و سیستمعامل است.
تاریخ ریچی و تامپسون همچنین یادآوری میکند که زبان و سیستمعامل میتوانند یکدیگر را شکل دهند. انتخاب زبان تنها انتخاب ظاهر کد نیست؛ روی ابزارها، کتابخانهها و شیوهٔ همکاری تیم نیز اثر میگذارد.
کارگاه کنترل نزدیک به ماشین
برای فهم مسئلهٔ زبان سیستمی، برنامهای را تصور کنید که باید یک بافر داده را بخواند، آن را پردازش کند و نتیجه را به یک ابزار دیگر تحویل دهد. توسعهدهنده هم به بیان قابل فهم نیاز دارد و هم باید بداند داده کجا قرار میگیرد و چه زمانی معتبر است. این تنش میان انتزاع و کنترل، نقطهٔ مناسبی برای مطالعهٔ میراث C است. مثالهای این بخش آموزشیاند و بخشی از کد تاریخی ریچی محسوب نمیشوند.
یک متغیر عددی و یک ناحیهٔ حافظه را روی کاغذ جدا رسم کنید. سپس مشخص کنید کدام نام به مقدار اشاره میکند و کدام نام آدرس را نگه میدارد. اگر دو بخش برنامه به یک ناحیه دسترسی داشته باشند، تغییر یکی ممکن است در دیگری دیده شود. همین مثال کوچک نشان میدهد چرا فهم مالکیت و طول عمر داده اهمیت دارد. داشتن قدرت دسترسی مستقیم، بدون قرارداد روشن دربارهٔ استفاده از آن، میتواند استدلال دربارهٔ برنامه را دشوار کند.
طول، ظرفیت و مالکیت داده
در طراحی یک رابط، طول دادهٔ معتبر را از ظرفیت فضای رزروشده جدا کنید. فرض کنید برای صد نویسه جا وجود دارد اما فقط دوازده نویسه مقداردهی شده است. خواندن تا انتهای ظرفیت با خواندن دادهٔ معتبر یکسان نیست. همچنین باید معلوم باشد چه کسی فضای حافظه را آزاد میکند و پس از آن چه اشارهگرهایی هنوز در گردشاند. این پرسشها فقط به نوشتن یک دستور مربوط نیستند؛ بخشی از قرارداد میان ماژولها هستند.
تمرین پیشنهادی این است که پیش از پیادهسازی تابع، سه سطر بنویسید: تابع چه ورودی معتبری میپذیرد، چه چیزی را تغییر میدهد و چه نتیجهای تحویل میدهد. سپس برای ورودی خالی و اندازهٔ مرزی مثال بسازید. وقتی قرارداد صریح باشد، انتخاب ابزار آزمون و بررسی ایستا نیز هدفمندتر میشود. نام زبان یا سابقهٔ موفق آن، جای این کار دقیق را نمیگیرد؛ هر پیادهسازی باید مسئولیتهای واقعی خودش را روشن کند.
قابلیت حمل، فراتر از کامپایلشدن
اجرای برنامه روی دو دستگاه نیازمند توافق بر سر فرضهای پنهان است. اندازهٔ نوع عددی، ترتیب بایت، قالب فایل و رفتار محیط میتوانند مهم باشند. اگر برنامه داده را با فرضی خاص ذخیره کند، محیط دوم باید همان قرارداد را بفهمد. به همین دلیل یک آزمون قابلیت حمل نباید فقط بررسی کند فایل اجرایی ساخته شده است؛ باید خروجی و معنای داده را نیز مقایسه کند.
یک تمرین امن، طراحی قالبی کوچک برای ذخیرهٔ چند عدد است. واحد، محدوده و ترتیب فیلدها را قبل از کدنویسی بنویسید. سپس یک خوانندهٔ مستقل برای همان قالب بسازید. اگر دو پیادهسازی با ورودی درست توافق نداشتند، نخست قرارداد را بررسی کنید. این تمرین نشان میدهد چگونه انتخابهای نزدیک به ماشین باید در مرزهای تبادل داده صریح شوند تا قدرت ابزار به وابستگی تصادفی تبدیل نشود.
رابطهٔ زبان و سیستمعامل
در مطالعهٔ C و Unix، دو محصول را جدا اما مرتبط ببینید. زبان، ابزار بیان برنامه است؛ سیستمعامل، مجموعهای از رفتارها و رابطهای اجرایی را فراهم میکند. همکاری این دو میتواند طراحی و انتقال سیستم را آسانتر کند، ولی هر کدام محدودیت و مسئولیت خود را دارند. منابع تاریخی این صفحه برای انتساب کار ریچی و همکاری با تامپسون مبنا هستند؛ مثالهای این بخش صرفاً روشی برای فهم مسئلهٔ مهندسیاند.
برای تمرین، یک ابزار کوچک بنویسید که از ورودی استاندارد بخواند و نتیجه را در خروجی استاندارد قرار دهد. پیام خطا را از دادهٔ خروجی جدا نگه دارید. سپس ابزار را در یک زنجیرهٔ پردازش قرار دهید و بررسی کنید شکست مرحلهٔ اول چگونه به مرحلهٔ بعد منتقل میشود. این آزمایش، قراردادهای سیستمعامل را از مفاهیم زبان جدا میکند و نشان میدهد طراحی رابط تا چه اندازه بر قابلیت ترکیب اثر میگذارد.
خواندن میراث بدون نادیدهگرفتن هزینهها
ارزش تاریخی یک ابزار با مناسببودن آن برای هر پروژهٔ تازه برابر نیست. انتخاب زبان باید نیاز عملکردی، تجربهٔ تیم، ابزارهای کنترل خطا و هزینهٔ نگهداری را در نظر بگیرد. برای یک مؤلفهٔ حساس، تنها سرعت نمونهٔ کوچک را مقایسه نکنید؛ هزینهٔ اثبات رفتار و بررسی حافظه نیز مهم است. چنین نگاهی نه دستاورد ریچی را کوچک میکند و نه مخاطرات را پنهان؛ میراث فنی را به منبعی برای تصمیم آگاهانه تبدیل میکند.