...

خواندن اطلاعات و کار با ورودی و فایل در زبان C

خواندن اطلاعات و کار با ورودی و فایل در زبان C
2 بازدید
۱۴۰۵-۰۸-۱۱
8 دقیقه
  • نویسنده: آرتین
  • درباره نویسنده: ---

در قسمت ۳۴ آموزش امبدد C با تابع printf، روش‌های قالب‌بندی خروجی، جدول ASCII و تفاوت دو جریان stdout و stderr آشنا شدیم. حالا نوبت آن است که مسیر برعکس را بررسی کنیم: چگونه اطلاعات را وارد برنامه کنیم، داده‌های دریافت‌شده را بررسی کنیم و آن‌ها را در یک فایل متنی ذخیره یا از فایل بخوانیم؟

در این قسمت از آموزش، مباحث بعدی فصل چهاردهم کتاب Bare Metal C را دنبال می‌کنیم. ابتدا با stdin و تابع fgets ورودی را دریافت می‌کنیم؛ سپس با sscanf داده‌های متنی را به مقدار عددی تبدیل می‌کنیم. بعد از بررسی دلیل ناامن‌بودن تابع قدیمی gets، با توابع fopen، fprintf، fgets و fclose یک فایل واقعی می‌سازیم و آن را می‌خوانیم.

خواندن اطلاعات در زبان C چگونه انجام می‌شود؟

تا اینجا بیشتر از برنامه خروجی گرفته‌ایم؛ مثلاً با printf متنی را در ترمینال نشان داده‌ایم. برای دریافت اطلاعات، یکی از جریان‌های استاندارد زبان C به نام stdin را در اختیار داریم. در یک برنامه کنسولی معمولی، stdin غالباً به صفحه‌کلید متصل است؛ با این حال سیستم‌عامل می‌تواند ورودی آن را از یک فایل یا جریان دیگر نیز تأمین کند.

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

خواندن اطلاعات و کار با ورودی و فایل در زبان C

 

شکل ۳۵-۱ ـ مسیر داده از صفحه‌کلید یا فایل ورودی به stdin، تابع fgets، بافر کاراکتری و سپس sscanf

خواندن ورودی با تابع fgets

تابع fgets از کتابخانه stdio.h یک رشته را از جریان ورودی می‌خواند و داخل آرایه‌ای از کاراکترها قرار می‌دهد. شکل کلی آن به صورت زیر است:

در این دستور، buffer حافظه مقصد، size ظرفیت بافر بر حسب بایت و stream جریان ورودی است. تابع حداکثر size - 1 نویسه می‌خواند تا یک بایت برای نویسه پایان رشته یعنی \0 باقی بماند. اگر کاراکتر پایان خط (\n) در این محدوده خوانده شود، معمولاً در رشته ذخیره می‌شود. در صورت پایان ورودی پیش از دریافت هر کاراکتر یا وقوع خطا، مقدار NULL برگردانده می‌شود.

مثال دریافت نام کاربر

در مثال زیر با استفاده از fgets نام کاربر را می‌خوانیم. سپس با کمک strcspn کاراکتر پایان خط را، در صورت وجود، حذف می‌کنیم تا خروجی مرتب‌تری داشته باشیم.

نمونه اجرا:

دستور fflush(stdout) باعث می‌شود متن درخواست نام، حتی در محیط‌هایی که خروجی بافر می‌شود، قبل از انتظار برای ورودی نمایش داده شود. استفاده از sizeof(name) هم ظرفیت واقعی آرایه را به تابع اعلام می‌کند.

نکته: اگر یک خط ورودی از ظرفیت بافر بزرگ‌تر باشد، fgets فقط بخشی از آن را می‌خواند و باقی داده در جریان ورودی می‌ماند. در برنامه‌های واقعی باید این وضعیت نیز مدیریت شود؛ از جمله با بررسی وجود \n و تخلیه ادامه خط در صورت لزوم.

تبدیل رشته به عدد با sscanf

گاهی ورودی صرفاً یک نام یا متن نیست؛ مثلاً می‌خواهیم کاربر دو عدد وارد کند یا از یک سطر متنی چند مقدار استخراج کنیم. تابع sscanf یک رشته موجود در حافظه را براساس الگوی قالب‌بندی تحلیل می‌کند. تفاوت آن با scanf این است که sscanf به‌جای خواندن مستقیم از stdin، داده را از یک رشته دریافت می‌کند.

برای مثال، الگوی "%d %d" به معنی خواندن دو عدد صحیح از یک رشته است. علامت & در مقابل متغیرها ضروری است، زیرا تابع باید مقدار تبدیل‌شده را در آدرس آن متغیرها ذخیره کند. مقدار بازگشتی sscanf تعداد تبدیل‌های موفق است.

مثال خواندن و بررسی دو عدد صحیح

نمونه ورودی و خروجی صحیح:

نمونه ورودی نامعتبر:

در این مثال وجود %c سوم صرفاً برای تشخیص متن اضافه بعد از دو عدد است. وقتی فقط دو عدد و فاصله‌های مجاز وارد شوند، تعداد تبدیل‌های موفق برابر ۲ خواهد بود. اما اگر کاراکتر اضافی وارد شود، تابع می‌تواند تبدیل سومی انجام دهد و برنامه آن را رد می‌کند.

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

چرا نباید از تابع gets استفاده کنیم؟

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

به همین دلیل، تابع gets از استاندارد C11 حذف شده است و نباید در پروژه‌های جدید از آن استفاده کرد. به‌جای آن از fgets همراه با بررسی ظرفیت بافر و مدیریت ورودی طولانی استفاده کنید.

خواندن اطلاعات و کار با ورودی و فایل در زبان C

 

شکل ۳۵-۲ ـ مقایسه تابع حذف‌شده gets با تابع fgets و محدودسازی طول داده برای جلوگیری از سرریز بافر

فایل در زبان C چیست و چگونه باز می‌شود؟

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

در زبان C برای مدیریت فایل‌های استاندارد از نوع FILE * استفاده می‌کنیم. این نوع، یک اشاره‌گر به ساختار مدیریتی جریان فایل در کتابخانه C است. برای بازکردن فایل از تابع fopen استفاده می‌شود:

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

حالت‌های مهم بازکردن فایل با fopen

حالت کاربرد رفتار مهم
r خواندن فایل فایل باید از قبل وجود داشته باشد.
w نوشتن فایل فایل جدید می‌سازد یا محتوای فایل موجود را پاک می‌کند.
a افزودن داده در انتهای فایل محتوای قبلی را نگه می‌دارد.
r+ خواندن و نوشتن فایل باید موجود باشد و به‌صورت خودکار پاک نمی‌شود.
w+ خواندن و نوشتن فایل را ایجاد می‌کند یا محتوای قبلی را حذف می‌کند.
a+ خواندن و افزودن داده نوشتن در انتهای فایل انجام می‌شود.

حرف b نیز در حالت‌هایی مثل rb و wb برای حالت باینری به‌کار می‌رود که موضوع قسمت بعدی آموزش است.

نکته مهم: استفاده از "w" روی فایلی که از قبل وجود دارد، محتوای قبلی آن را حذف می‌کند. اگر قصد دارید فقط اطلاعات جدید را به انتهای گزارش اضافه کنید، حالت "a" مناسب‌تر است.

خواندن اطلاعات و کار با ورودی و فایل در زبان C

شکل ۳۵-۳ ـ مقایسه عملکرد حالت‌های r، w، a، r+، w+ و a+ در تابع fopen

مثال عملی ساخت و نوشتن فایل متنی در زبان C

فرض کنید می‌خواهیم نتایج اندازه‌گیری دما را در یک فایل متنی نگه داریم. در این مثال، فایلی با نام sensor_log.txt ایجاد می‌کنیم و سه سطر اطلاعات در آن قرار می‌دهیم. برای نوشتن متن در فایل از fprintf استفاده می‌کنیم؛ عملکردی شبیه printf دارد، اما مقصد خروجی را صریحاً مشخص می‌کنیم.

خروجی ترمینال:

پس از اجرای موفق، فایل sensor_log.txt معمولاً در پوشه کاری فعلی برنامه ساخته می‌شود. محتوای آن به این شکل خواهد بود:

در این کد چند نکته مهم وجود دارد: ابتدا نتیجه fopen بررسی می‌شود؛ سپس خروجی‌های fprintf کنترل می‌شوند؛ و در پایان از fclose برای بستن فایل استفاده می‌کنیم. بررسی نتیجه بستن فایل مهم است، چون ممکن است خطای نوشتن بافرشده در زمان بسته‌شدن آشکار شود.

خواندن محتوای یک فایل متنی با fgets

حالا همان فایل ساخته‌شده در بخش قبل را می‌خوانیم. این‌بار fopen را با حالت "r" فراخوانی می‌کنیم و در یک حلقه، هر بار قسمتی از فایل را تا پایان خط یا پرشدن بافر دریافت می‌کنیم. تابع fputs نیز متن خوانده‌شده را بدون افزودن خودکار کاراکتر پایان خط در خروجی نمایش می‌دهد.

خروجی:

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

خواندن اطلاعات و کار با ورودی و فایل در زبان C

شکل ۳۵-۴ ـ جریان ذخیره داده با fopen و fprintf و بازیابی اطلاعات با fopen و fgets و بستن فایل با fclose

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

مثال‌های این قسمت را می‌توانید روی یک کامپیوتر دارای سیستم‌عامل با کامپایلر GCC اجرا کنید. در پروژه‌های Embedded Linux نیز همین توابع استاندارد C برای خواندن تنظیمات، ثبت لاگ و پردازش فایل‌ها کاربرد گسترده‌ای دارند.

اما در یک میکروکنترلر Bare-metal مانند STM32، معمولاً فایل‌سیستم و دیسک به‌صورت پیش‌فرض وجود ندارند. برای استفاده از فایل‌ها ممکن است به حافظه SD، فایل‌سیستمی مانند FatFs و درایور ذخیره‌سازی نیاز داشته باشیم. در چنین شرایطی صرفاً نوشتن fopen کافی نیست و باید کتابخانه C یا لایه فایل‌سیستم را به سخت‌افزار متصل کنیم.

به‌عنوان نمونه، یک دیتالاگر مبتنی بر STM32 می‌تواند داده سنسور دما را دریافت و روی کارت حافظه ذخیره کند؛ ولی برای این کار علاوه بر منطق برنامه، به راه‌اندازی رابط سخت‌افزاری و فایل‌سیستم نیز نیاز خواهیم داشت. پس تفاوت زبان C و کتابخانه استاندارد با امکانات واقعی سخت‌افزار و سیستم‌عامل را همیشه در نظر بگیرید.

نکات کاربردی و خطاهای رایج

  • بررسی بازشدن فایل: نتیجه fopen را با NULL مقایسه کنید و در صورت خطا پیام مناسبی نمایش دهید.
  • انتخاب حالت مناسب: حالت w فایل موجود را خالی می‌کند؛ برای اضافه‌کردن داده از a استفاده کنید.
  • کنترل طول ورودی: تابع fgets ظرفیت بافر را رعایت می‌کند، اما ممکن است بقیه خط در ورودی باقی بماند.
  • بررسی نتیجه تبدیل: تعداد تبدیل‌های موفق sscanf را بررسی کنید؛ دریافت رشته به معنی معتبر بودن محتوای آن نیست.
  • بستن فایل: بعد از اتمام کار، fclose را اجرا و نتیجه آن را بررسی کنید.
  • مسیر فایل: در ویندوز برای نوشتن مسیر در رشته C، بک‌اسلش باید Escape شود؛ مانند "C:\\logs\\sensor_log.txt". برای مسیرهای قابل‌حمل تا حد امکان از مسیر نسبی استفاده کنید.

جمع بندی

در قسمت ۳۵ آموزش امبدد C یاد گرفتیم که خواندن ورودی بهتر است در دو مرحله انجام شود: ابتدا دریافت یک رشته با fgets و سپس بررسی و تبدیل داده با sscanf. همچنین دیدیم چرا gets تابع امنی نیست و چگونه می‌توان با fopen یک فایل متنی را باز کرد، با fprintf در آن نوشت، با fgets از آن خواند و در پایان با fclose آن را بست.

در قسمت ۳۶ به ادامه فصل چهاردهم می‌پردازیم و سراغ فایل‌های باینری، توابع fread و fwrite، کپی‌کردن فایل و مفاهیم Buffering و Flushing می‌رویم.

تمرین

  1. برنامه دریافت نام را طوری تغییر دهید که اگر کاربر فقط Enter زد، پیام «نام نمی‌تواند خالی باشد» نمایش داده شود.
  2. برنامه دریافت دو عدد را به‌گونه‌ای گسترش دهید که به‌جای جمع، حاصل‌ضرب آن‌ها را نیز چاپ کند.
  3. برنامه ثبت دما را با حالت a بازنویسی کنید تا با هر بار اجرای آن، سطر جدیدی به فایل اضافه شود و سطرهای قبلی پاک نشوند.
  4. برنامه خواندن فایل را تغییر دهید تا تعداد سطرهای خوانده‌شده را نیز نمایش دهد؛ فرض کنید هر سطر در بافر جا می‌شود.

منبع آموزشی: کتاب Bare Metal C: Embedded Programming for the Real World نوشته Stephen Oualline، فصل ۱۴ (بخش‌های Reading Data، The Evil gets Function و Opening Files). مثال‌های این مقاله با نگارش آموزشی مستقل بازنویسی شده‌اند.

اطلاعات
2
0
0
اشتراک و حمایت
profile نویسنده: آرتین متخصص الکترونیک

ویراستار: آرتین
مقالات بیشتر

slide

پالت | بازار خرید و فروش قطعات الکترونیک

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

آیسی | موتور جستجوی قطعات الکترونیک

سامانه آی سی سیسوگ (Isee) قابلیتی جدید و کاربردی از سیسوگ است. در این سامانه سعی شده است که جستجو، انتخاب و خرید مناسب تر قطعات برای کاربران تسهیل شود. جستجو در آیسی
family

سیسوگ‌شاپ | فروشگاه محصولات Quectel

فروشگاه سیسوگ مجموعه ای متمرکز بر تکنولوژی های مبتنی بر IOT و ماژول های M2M نظیر GSM، GPS، LTE، NB-IOT، WiFi، BT و ... جایی که با تعامل فنی و سازنده، بهترین راهکارها انتخاب می شوند. برو به فروشگاه سیسوگ
family

سیسوگ فروم | محلی برای پاسخ پرسش‌های شما

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

سیکار | اولین مرجع متن باز ECU در ایران

بررسی و ارائه اطلاعات مربوط به ECU (واحد کنترل الکترونیکی) و نرم‌افزارهای متن باز مرتبط با آن برو به سیکار
become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله

خانواده سیسوگ

سیسوگ‌شاپ

فروشگاه محصولات Quectel

پالت
سیسوگ فروم

محلی برای پاسخ پرسش‌های شما

سیسوگ جابز
سیسوگ
سیسوگ فروم
سی‌کار

اولین مرجع متن باز ECU در ایران

سیسوگ مگ
آی‌سی

موتور جستجوی قطعات الکترونیکی

سیسوگ آکادمی
پالت

بازار خرید و فروش قطعات الکترونیک

دیدگاه ها

become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله