Faragiri

همهٔ زبان‌ها

آموزش اشاره‌گر و حافظه سی، سی‌پلاس‌پلاس و سی‌شارپ

آرایه وقتی به تابع پاس داده شود به اشاره‌گر تبدیل می‌شود و طولش را پشت سر جا می‌گذارد. برای همین هر تابع سی کنار آرایه یک شمارنده می‌گیرد.

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

یک اشاره‌گر در دست

چه چیزی را از یک اشاره‌گر می‌شود پرسید و چه چیزی را نه

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

  1. بلهآدرسی که در آن نشستهیک عدد، و روی این ماشین هشت بایتبله
  2. بلهنوع چیزی که به آن اشاره می‌کنداز خود اعلان می‌آید و حساب اشاره‌گر را تعیین می‌کندبله
  3. خیرمقدار همان یک خانهبا مرجع‌زدایی، اگر آدرس معتبر باشدخیر
  4. خیرطول آرایه‌ای که به آن اشاره می‌کندهیچ راهی نیست، و برای همین شمارنده جدا لازم استخیر
  5. خیراینکه هنوز معتبر است یا آزاد شدهعدد پس از آزادسازی همان می‌ماند و هیچ چیزی عوض نمی‌شودخیر
  6. بررسی نشدهاینکه صاحبش کیستفقط قرارداد تیم می‌گویدش، و در خود زبان نوشته نشدهبررسی نشده

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

یک آرایه چهارتایی از عدد صحیح. در تابع اصلی، sizeof عدد ۱۶ می‌دهد، یعنی چهار خانه چهاربایتی. همان آرایه را به یک تابع پاس دهید و داخل آن تابع همان عملگر عدد ۸ می‌دهد.

اندازه‌گیری‌شده با Apple clang 21.0.0 در تاریخ ۲۰۲۶-۰۹-۰۳. عدد ۸ اندازه آرایه نیست، اندازه یک اشاره‌گر است. آرایه هنگام پاس دادن به اشاره‌گر به اولین عضوش تبدیل می‌شود و طول را همان‌جا جا می‌گذارد.

نتیجه‌ای که از این می‌آید یک قاعده است و استثنا هم ندارد: هر تابعی در سی که آرایه می‌گیرد، باید یک شمارنده هم کنارش بگیرد. هیچ راهی برای بازیابی طول از خود اشاره‌گر وجود ندارد.

حساب اشاره‌گر با واحد نوع کار می‌کند

افزودن یک به یک اشاره‌گر، آدرس را به اندازه یک عضو جلو می‌برد و نه یک بایت. با sizeof(int) برابر ۴، فاصله دو خانه پشت سر هم ۴ بایت است. برای همین p + 1 روی یک اشاره‌گر به عدد صحیح چهار بایت جلو می‌رود و روی اشاره‌گر به یک ساختار بزرگ‌تر، به اندازه همان ساختار.

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

سه حالتی که مقدارها در آن‌ها زندگی می‌کنند

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

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

جایی که کار گیر می‌کند، تمرین‌های بدون ابزار است

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

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

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

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

کنار این

بالای این