اذهب إلى المحتوى

مقدمة عن التعامل مع الدخل والخرج I/O في لغة سي C


Naser Dakhel

يعدّ افتقار لغات البرمجة لدعمها للدخل والخرج إحدى أبرز الأسباب التي منعت التبني واسع النطاق واستخدامها في البرمجة العملية، وهو الموضوع الذي لم يرد أي مصمّم لغة أن يتعامل معه، إلا أن لغة سي تفادت هذه المشكلة، بعدم تضمينها لأي دعم للدخل والخرج، إذ كان سلوك سي هو أن تترك التعامل مع الدخل والخرج لدوال المكتبات، مما عنى أنه بالإمكان لمصمّمي الأنظمة استخدام طرق دخل وخرج مخصصة بدلًا من إجبارهم على تغيير اللغة بذات نفسها.

تطوّرت حزمة مكتبات عُرفت باسم "مكتبة الدخل والخرج القياسي Standard I/O Library" -أو اختصارًا stdio- في الوقت ذاته الذي كانت لغة سي تتطوّر، وقد أثبتت هذه المكتبة مرونتها وقابلية نقلها وأصبحت الآن جزءًا من المعيار.

اعتمدت حزمة الدخل والخرج القياسي القديمة كثيرًا على نظام يونيكس UNIX للوصول إلى الملفات وبالأخص الافتراض أنه لا يوجد أي فرق بين ملفات ثنائية غير مُهيكلة وملفات أخرى تحتوي على نص مقروء، إلا أن العديد من أنظمة التشغيل تفصل ما بين الاثنين وعُدّلت الحزمة فيما بعد لضمان قابلية نقل برامج لغة سي بين نوعَي نظام الملفات. هناك بعض التغييرات في هذا المجال التي تتسبب بالضرر لكثيرٍ من البرامج المكتوبة مسبقًا على الرغم من الجهود التي تحاول أن تحدّ من هذا الضرر.

من المفترض أن تعمل برامج لغة سي القديمة بنجاح دون تعديل في بيئة يونيكس.

نموذج الدخل والخرج

لا يُميّز نموذج الدخل والخرج بين أنواع الأجهزة المادية التي تدعم الدخل والخرج، إذ يُعامل كل مصدر أو حوض من البيانات بالطريقة ذاتها ويُنظر إليه على أنه مجرًى من البايتات stream of bytes. بما أن الكائن الأصغر الذي يمكن تمثيله في لغة سي هو المحرف، فالوصول إلى الملف مسموحٌ باستخدام حدود أي محرف، وبالتالي يمكن قراءة أو كتابة أي عدد من المحارف انطلاقًا من نقطة متحركة تُعرف باسم مؤشر الموضع position indicator، وتُكتب أو تُقرأ المحارف تباعًا بدءًا من هذه النقطة ويُحرّك مؤشر الموضع خلال ذلك. يُضبط مؤشر الموضع مبدئيًا إلى بداية الملف عند فتحه، لكن من الممكن تحريكه باستخدام طلبات تحديد الموقع، ويُتجاهل مؤشر موضع الملف في حال كان الوصول العشوائي إلى الملف غير ممكن. لفتح الملف في نمط الإضافة append تأثيرات على مجرى موضع المؤشر في الملف معرفة بحسب التنفيذ.

الفكرة العامة هي تقديم إمكانية القراءة أو الكتابة بصورةٍ تتابعية، باستثناء حالة فتح المجرى باستخدام نمط الإضافة، أو إذا حُرّك مؤشر موضع الملف مباشرةً.

هناك نوعان من أنواع الملفات، هما: الملفات النصية text files والملفات الثنائية binary files التي يمكن التعامل معها داخل البرنامج على أنها مجاري نصية text streams أو مجاري ثنائية binary streams بعد فتحها لعمليات الإدخال والإخراج. لا تسمح حزمة stdio بالعمليات على محتوى الملف مباشرةً، بل بالتعديل على المجرى الذي يحتوي على بيانات الملف.

المجاري النصية

يحدّد المعيار المجرى النصي text stream، الذي يمثّل ملفًا يحتوي على أسطر نصية ويتألف السطر من صفر محرف أو أكثر ينتهي بمحرف نهاية السطر، ومن الممكن أن يكون تمثيل الأسطر الفعلي في البيئة الخارجية مختلفًا عن تمثيله هنا، كما من الممكن إجراء تحويلات على مجرى البيانات عند دخولها إلى أو خروجها من البرنامج، وأكثر المتطلبات شيوعًا هو ترجمة المحرف الذي ينهي السطر "'‎\n'" إلى السلسلة "'‎\r\n'" عند الخرج وإجراء عكس العملية عند الدخل، ومن الممكن تواجد بعض الترجمات الضرورية الأخرى.

يُضمن للبيانات التي تُقرأ من المجرى النصي أن تكون مساويةً إلى البيانات المكتوبة سابقًا إلى الملف، وذلك إذا كانت هذه البيانات مؤلفةً من أسطر مكتملة تحتوي على محارف يمكن طباعتها، وكانت محارف التحكم control characters ومحارف مسافة الجدولة الأفقية horizontal-tab ومحارف الأسطر الجديدة newline فقط، ولم يُتبع أي محرف سطر جديد بمحرف مسافة فارغة space مباشرةً، وكان المحرف الأخير في المجرى هو محرف سطر جديد.

كما أن هناك ضمان بأن المحرف الأخير المكتوب إلى الملف النصي هو محرف سطر جديد، ومن الممكن قراءة الملف مجددًا بمحتوياته المماثلة التي كُتبت إليه سابقًا.

إلحاق المحرف الأخير المكتوب إلى الملف بمحرف سطر جديد معرفٌ بحسب التنفيذ، وذلك لأن الملفات النصية والملفات الثنائية تُعامل نفس المعاملة في بعض التنفيذات.

قد تُجرّد بعض التنفيذات المسافة الفارغة البادئة من الأسطر التي تتألف من مسافات فارغة فقط متبوعةً بسطر جديد، أو تُجرّد المسافة الفارغة في نهاية السطر.

يجب أن يدعم التنفيذ الملفات النصية التي تحتوي سطورها على 254 محرفًا على الأقل، ويتضمن ذلك محرف السطر الجديد الذي يُنهي السطر.

قد نحصل على مجرى ثنائي عند فتح مجرى نصي بنمط التحديث update mode في بعض التنفيذات.

قد تتسبب الكتابة على مجرًى نصي باقتطاع الملف عند نقطة الكتابة في بعض التنفيذات، أي ستُهمل جميع البيانات التي تتبع البايت الأخير المكتوب.

المجاري الثنائية

يمثل المجرى الثنائي سلسلةً من المحارف التي يمكن استخدامها لتسجيل البيانات الداخلية لبرنامج ما، مثل محتويات الهياكل structures، أو المصفوفات وذلك بالشكل الثنائي، إذ تكون البيانات المقروءة من المجاري الثنائية مساويةً للبيانات المكتوبة إلى المجرى ذاته سابقًا ضمن نفس التنفيذ، وقد يُضاف عددٌ من المحارف الفارغة "NULL" في بعض الظروف إلى نهاية المجرى الثنائي، ويكون عدد المحارف معرفًا بحسب التنفيذ.

تعتمد بيانات الملفات الثنائية على الآلة التي تعمل عليها لأبعد حد، وهي غير قابلة للنقل عمومًا.

المجاري الأخرى

قد تتوفر بعض أنواع المجاري الأخرى، إلا أنها معرفة بحسب التنفيذ.

ملف الترويسة <stdio.h>

هناك عدد من الدوال والماكرو الموجودة لتقديم الدعم لمختلف أنواع المجاري، ويحتوي ملف الترويسة <stdio.h> العديد من التصريحات المهمة لهذه الدوال، إضافةً إلى الماكرو التالية وتصاريح الأنواع:

  • النوع FILE: نوع الكائن المُستخدم لاحتواء معلومات التحكم بالمجرى، ولا يحتاج مستخدمو مكتبة "stdio" لمعرفة محتويات هذه الكائنات، إذ يكفي التعامل مع المؤشرات التي تشير إليهم. لا يُعد نسخ الكائنات هذه ضمن البرنامج آمنًا، إذ أن عناوينهم قد تكون في بعض الأحيان معقدة.
  • النوع fpos_t: نوع الكائن الذي يُستخدم لتسجيل القيم الفريدة من نوعها التي تنتمي إلى مجرى مؤشر موضع الملف.
  • القيم IOFBF_ و IOLBF_ و IONBF_: وهب قيم تُستخدم للتحكم بالتخزين المؤقت buffering للمجرى بالاستعانة بالدالة setvbuf.
  • القيمة BUFSIZ: حجم التخزين المؤقت المُستخدم بواسطة الدالة setbuf، وهو تعبيرٌ رقم صحيح integral ثابت constant تكون قيمته 256 على الأقل.
  • القيمة EOF: تعبير رقم صحيح سالب ثابت يحدد نهاية الملف end-of-file ضمن مجرى، أي عند الوصول إلى نهاية الدخل.
  • القيمة FILENAME_MAX: الطول الأعظمي الذي يمكن لاسم ملف أن يكون إذا كان هناك قيد على ذلك، وإلا فهو الحجم الذي يُنصح به لمصفوفة تحمل اسم ملف.
  • القيمة FOPEN_MAX: العدد الأدنى من الملفات التي يضمن التنفيذ فتحها في وقت آني، وهو ثمانية ملفات. لاحظ أنه من الممكن إغلاق ثلاث مجاري مُعرفة مسبقًا إذا احتاج البرنامج فتح أكثر من خمسة ملفات مباشرةً.
  • القيمة L_tmpnam: الطول الأعظمي المضمون لسلسلة نصية في tmpnam، وهو تعبير رقم صحيح ثابت.
  • القيم SEEK_CUR و SEEK_END و SEEK_SET: تعابير رقم صحيح ثابتة تُستخدم للتحكم بأفعال fseek.
  • القيمة TMP_MAX: العدد الأدنى من أسماء الملفات الفريدة من نوعها المولدة من قبل tmpnam، وهو تعبير رقم صحيح ثابت بقيمة لا تقل عن 25.
  • الكائنات stdin و stdout و stderr: وهي كائنات معرفة مسبقًا من النوع "* FILE" وتشير إلى مجرى الدخل القياسي ومجرى الخرج القياسي ومجرى الخطأ بالترتيب، وتُفتح هذه المجاري تلقائيًا عند بداية تنفيذ البرنامج.

العمليات على المجاري

بعد أن تعرفنا على أنواع المجاري وطبيعتها والقيم المرتبطة بها، نستعرض الآن العمليات الأساسية عليها ألا وهي فتح المجرى وإغلاقه، إضافةً إلى التخزين المؤقت.

فتح المجرى

يتصل المجرى بالملف عن طريق دالة fopen، أو freopen، أو tmpfile، إذ تعيد هذه الدوال -إذا كان استدعاؤها ناجحًا- مؤشرًا يشير إلى كائن من نوع FILE.

هناك ثلاث أنواع من المجاري المتاحة افتراضيًا دون أي جهد إضافي مطلوب منك، وتتصل هذه المجاري عادةً بالجهاز المادي المُرتبط بالبرنامج المُنفّذ ألا وهو الطرفية Terminal عادةً، ويشار إلى هذه المجاري بالأسماء:

  • stdin: وهو مجرى الدخل القياسي standard input.
  • stdout: وهو مجرى الخرج القياسي standard output.
  • stderr: وهو مجرى الخطأ القياسي standard error.

ويكون دخل لوحة المفاتيح في الحالة الطبيعية من المجرى stdin وخرج الطرفية هو stdout، بينما تُوجّه رسائل الأخطاء إلى المجرى stderr. الهدف من فصل رسائل الأخطاء عن رسائل الخرج العادية هو السماح بربط مجرى stdout إلى شيءٍ آخر مغاير لجهاز للطرفية مثل ملف ما والحصول على رسائل الخطأ بنفس الوقت على الشاشة أمامك عوضًا عن توجيه الأخطاء إلى الملف، وتخزّن كامل الملفات مؤقتًا إذا لم توجّه إلى أجهزة تفاعلية.

كما ذكرنا سابقًا، قد يكون مؤشر موضع الملف قابلًا للتحريك أو غير قابل للتحريك بحسب الجهاز المُستخدم، إذ يكون مؤشر موضع الملف غير قابل للتحريك ضمن مجرى stdin على سبيل المثال إذا كان متصلًا إلى الطرفية (الحالة الاعتيادية له).

جميع الملفات غير المؤقتة تمتلك اسمًا filename وهو سلسلةٌ نصية، والقوانين التي تحدد اسم الملف الصالح معرفةٌ حسب التنفيذ، وينطبق الأمر ذاته على إمكانية فتح الملف لعدة مرات بصورةٍ آنية. قد يتسبب فتح ملف جديد بإنشاء هذا الملف، وتتسبب إعادة إنشاء ملف موجود مسبقًا بإهمال محتوياته السابقة.

إغلاق المجرى

تُغلق المجاري عند استدعاء fclose أو exit بصورةٍ صريحة، أو عندما يعود البرنامج إلى الدالة main، وتُمسح جميع البيانات المخزنة مؤقتًا عند إغلاق المجرى. تصبح حالة الملفات المفتوحة غير معروفة إذا توقف البرنامج لسببٍ ما دون استخدام الطرق السابقة لإغلاقه.

التخزين المؤقت للمجرى

هناك ثلاث أنواع للتخزين المؤقت:

  1. دون تخزين مؤقت unbuffered: تُستخدم مساحة التخزين بأقل ما يمكن من قبل stdio بهدف إرسال أو تلقي البيانات أسرع ما يمكن.
  2. تخزين مؤقت خطي line buffered: تُعالج المحارف سطرًا تلو سطر، ويُستخدم هذا النوع من التخزين المؤقت كثيرًا في البيئات التفاعلية، وتُمسح محتويات الذواكر المؤقتة الداخلية internal buffers فقط عندما تمتلئ أو عندما يُعالج سطر جديد.
  3. التخزين المؤقت الكامل fully buffered: تُسمح الذواكر المؤقتة الداخلية فقط عندما تمتلئ.

يُمكن مسح محتوى الذاكرة الداخلية المرتبطة بمجرى ما عن طريق استخدام fflush مباشرةً. يُعرَّف الدعم لأنواع التخزين المؤقت المختلفة بحسب التنفيذ، ويمكن التحكم به ضمن الحدود المعرفة باستخدام setbuf و setvbuf.

التلاعب بمحتويات الملف مباشرة

هناك عدة دوال تسمح لنا بالتعامل مع الملف مباشرةً.

#include <stdio.h>

int remove(const char *filename);
int rename(const char *old, const char *new);
char *tmpnam(char *s);
FILE *tmpfile(void);
  • الدالة remove: تتسبب بإزالة الملف، وستفشل محاولات فتح هذا الملف لاحقًا إلا في حال إنشاء الملف مجددًا. يكون سلوك الدالة remove عندما يكون الملف مفتوحًا معرفًا بحسب التنفيذ، وتعيد الدالة القيمة صفر للدلالة على النجاح، بينما تدل أي قيمة أخرى على فشل عملها.
  • الدالة rename: تُغيّر اسم الملف المعرف بالكلمة old في مثالنا السابق إلى new، وستفشل محاولات فتح الملف باستخدام اسمه القديم، إلا إذا أنشئ ملفٌ جديد يحمل الاسم القديم ذاته، وكما هو الحال في remove فإن الدالة rename تُعيد القيمة صفر للدلالة على نجاح العملية وأي قيمة مغايرة لذلك تدل على حصول خطأ. السلوك معرف حسب التنفيذ إذا حاولنا تسمية الملف باسم جديد باستخدام rename وكان هناك ملف بالاسم ذاته مسبقًا. لن يُعدّل على الملف إذا فشلت الدالة rename لأي سببٍ كان.
  • الدالة tmpnam: تولّد سلسلة نصية لتُستخدم اسمًا لملف، ويضمن لهذه السلسلة النصية أن تكون فريدةً من نوعها بالنسبة لأي اسم ملف آخر موجود، ويمكن أن تُستدعى بصورةٍ متتالية للحصول على اسم جديد كل مرة. يُستخدم الثابت TMP_MAX لتحديد عدد مرات استدعاء الدالة tmpnam قبل أن يتعذر عليه العثور على اسماء فريدة، وقيمته 25 على الأقل، ونحصل على سلوك غير معرّف من قبل المعيار في حال استدعاء الدالة tmpnam عدد مرات يتجاوز هذا الثابت إلا أن الكثير من التنفيذات تقدم حدًّا لا نهائيًا. تستخدم tmpnam ذاكرة مؤقتة داخلية لبناء الاسم وتُعيد مؤشرًا يشير إليه وذلك إذا ضُبط الوسيط s إلى القيمة NULL، وقد تغيّر الاستدعاءات اللاحقة للدالة الذاكرة المؤقتة الداخلية ذاتها. يمكن استخدام مؤشر يشير إلى مصفوفة مثل وسيط بدلًا من السابق، بحيث تحتوي المصفوفة على L_tmpnam محرف على الأقل، وفي هذه الحالة سُيملأ الاسم إلى الذاكرة المؤقتة المزوّدة (المصفوفة)، ويمكن فيما بعد إنشاء ملف بهذا الاسم واستخدامه ملفًا مؤقتًا. لن يكون اسم الملف مفيدًا ضمن سياقات أخرى غالبًا، بالنظر إلى توليده من قبل الدالة. لا تُزال الملفات المؤقتة من هذا النوع إلا إن استدعيت دالة الحذف، وغالبًا ما تُستخدم هذه الملفات لتمرير البيانات المؤقتة بين برنامجين منفصلين.
  • الدالة tmpfile: تُنشئ ملف ثنائي مؤقت يُمكن التعديل على محتوياته، وتعيد الدالة مؤشرًا يشير إلى مجرى الملف، ويُزال هذا الملف فيما بعد عند إغلاق مجراه، وتُعيد الدالة tmpfile مؤشرًا فارغًا null إذا لم ينجح فتح الملف.

فتح الملفات بالاسم

يمكن فتح الملفات الموجودة بالاسم عن طريق استدعاء الدالة fopen المصرّح عنها على النحو التالي:

#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);

يمثل الوسيط pathname اسم الملف الذي تريد فتحه، مثل الاسم الذي تعيده الدالة tmpnam أو أي اسم ملف معين آخر.

يمكن فتح الملفات باستخدام عدة أنماط modes، مثل نمط القراءة read لقراءة البيانات، ونمط الكتابة write لكتابة البيانات وهكذا.

لاحظ أن الدالة fopen ستُنشئ ملفًا إذا أردت كتابة البيانات على ملف، أو أنها ستتخلص من محتويات الملف إذا وُجد ليصبح طوله صفر (أي أنك ستخسر محتويات الملف السابقة).

يوضح الجدول التالي جميع الأنماط الموجودة في المعيار، إلا أن التنفيذ قد يسمح بأنماط أخرى بإضافة محارف إضافية في نهاية كل من الأنماط.

النمط نوع الملف القراءة الكتابة إنشاء جديد حذف القيمة السابقة
"r" نصي نعم لا لا لا
"rb" ثنائي نعم لا لا لا
"r+‎" نصي نعم نعم لا لا
"r+b" ثنائي نعم نعم لا لا
"rb+‎" ثنائي نعم نعم لا لا
"w" نصي لا نعم نعم نعم
"wb" ثنائي لا نعم نعم نعم
"w+‎" نصي نعم نعم نعم نعم
"w+b" ثنائي نعم نعم نعم نعم
"wb+‎" ثنائي نعم نعم نعم نعم
"a" نصي لا نعم نعم لا
"ab" ثنائي لا نعم نعم لا
"a+‎" نصي نعم نعم نعم لا
"a+b" ثنائي لا نعم نعم لا
"ab+‎" ثنائي لا نعم نعم لا

انتبه من بعض التنفيذات التي تضيف إلى النمط الأخير محارف NULL في حالة الملفات الثنائية، إذ قد يتسبب فتح هذه الملفات بالنمط ab أو +ab أو a+b بوضع مؤشر الملف خارج نطاق آخر البيانات المكتوبة.

تُكتب جميع البيانات في نهاية الملف إذا فُتح باستخدام نمط الإضافة append، بغض النظر عن محاولة تغيير موضع المؤشر باستخدام الدالة fseek، ويكون موضع مؤشر الملف المبدئي معرف بحسب التنفيذ.

تفشل محاولات فتح الملف بنمط القراءة (النمط 'r')، إذا لم يكن الملف موجودًا أو لم يمكن قراءته.

يمكن القراءة من والكتابة إلى الملفات المفتوحة بنمط التحديث update (باستخدام '+' مثل المحرف الثاني أو الثالث ضمن النمط) إلا أنه من غير الممكن إلحاق القراءة بالكتابة مباشرةً أو الكتابة بالقراءة دون استدعاء بينهما لدالة واحدة (أو أكثر) من الدوال: fflush أو fseek أو fsetpos أو rewind، والاستثناء الوحيد هنا هو جواز إلحاق الكتابة مباشرةً بعد القراءة إذا قُرأ المحرف EOF (نهاية الملف).

من الممكن أيضًا في بعض التنفيذات أن يُتخلى عن b في أنماط فتح الملفات الثنائية واستخدام الأنماط ذاتها الخاصة بالملفات النصية.

تُخزّن المجاري المفتوحة باستخدام fopen تخزينًا مؤقتًا بالكامل إذا لم تكن متصلة إلى جهاز تفاعلي، ويضمن ذلك التعامل مع الأسئلة prompts والطلبات responses على النحو الصحيح.

تعيد الدالة fopen مؤشرًا فارغًا null إذا فشلت بفتح الملف، وإلا فتعيد مؤشرًا يشير إلى الكائن الذي يتحكم بالمجرى. كائنات المجاري stdin و stdout و stderr غير قابلة للتعديل بالضرورة ومن الممكن عدم وجود إمكانية استخدام القيمة المُعادة من الدالة fopen لإسنادها إلى واحدة من هذه الكائنات، بدلًا من ذلك نستخدم freopen لهذا الغرض.

الدالة freopen

تُستخدم الدالة freopne لأخذ مؤشر يشير إلى مجرى وربطه مع اسم ملف آخر، وتصرَّح الدالة على النحو التالي:

#include <stdio.h>
FILE *freopen(const char *pathname,
              const char *mode, FILE *stream);

وسيط mode مشابه لمثيله في دالة fopen. يُغلق المجرى stream أولًا ويحدث تجاهل أي أخطاء متولدة عن ذلك، ونحصل على قيمة NULL في حالة حدوث خطأ عند تنفيذ الدالة، وإلا فإننا نحصل على القيمة الجديدة للمجرى stream.

إغلاق الملفات

يمكننا إغلاق ملف مفتوح باستخدام الدالة close والمصرح عنها كما يلي:

#include <stdio.h>

int fclose(FILE *stream);

يُتخلّص من أي بيانات موجودة على الذاكرة المؤقتة لم تُكتب على الملف الخاص بالمجرى stream إضافةً إلى أي بيانات أخرى لم تُقرأ، وتُحرّر الذاكرة المؤقتة المرتبطة بالمجرى إذا رُبطت به تلقائيًا، وأخيرًا يُغلق الملف.

نحصل على القيمة صفر للدلالة على نجاح العملية، وإلا فالقيمة EOF للدلالة على الخطأ.

الدالتان setbuf و setvbuf

تُستخدم الدالتان للتعديل على استراتيجية التخزين المؤقتة لمجرى معين مفتوح، ويُصرّح عن الدالتين كما يلي:

#include <stdio.h>

int setvbuf(FILE *stream, char *buf,
              int type, size_t size);
void setbuf(FILE *stream, char *buf);

يجب استخدام الدالتين قبل قراءة الملف أو الكتابة إليه، ويعرف الوسيط type نوع التخزين المؤقت للمجرى stream، ويوضح الجدول التالي أنواع التخزين المؤقت.

القيمة التأثير
‎_IONBF لا تخزّن الدخل والخرج مؤقتًا
‎_IOFBF خزِّن الدخل والخرج مؤقتًا
‎_IOLBF تخزين مؤقت خطي: تخلص من محتويات الذاكرة المؤقتة عندما تمتلئ، أو عند كتابة سطر جديد، أو عند طلب القراءة

يمكن للوسيط buf أن يكون مؤشرًا فارغًا، وفي هذه الحالة تُنشأ مصفوفة تلقائيًا لتخزين البيانات مؤقتًا، ويمكن بخلاف ذلك للمستخدم توفير ذاكرة مؤقتة لكن يجب التأكد من استمرارية الذاكرة المؤقتة بقدر مساوٍ (أو أكثر) لاستمرارية التدفق stream. يُعد استخدام مساحة التخزين المحجوزة تلقائيًا ضمن تعليمة مركبة compound statement من الأخطاء الشائعة، إذ أن الحصول على المساحة التخزينية على النحو الصحيح في هذه الحالة يجري عن طريق الدالة malloc عوضًا عن ذلك. يُحدد حجم الذاكرة المؤقتة باستخدام الوسيط size.

يشابه استدعاء الدالة setbuf استدعاء الدالة setvbuf إذا استخدمنا ‎_IOFBF قيمةً للوسيط type والقيمة BUFSIZ للوسيط size، وتُستخدم القيمة ‎_IONBF للوسيط type إذا كان buf مؤشرًا فارغًا.

لا تُعاد أي قيمة بواسطة الدالة setbuf، بينما تُعيد الدالة setvbuf القيمة صفر للدلالة على نجاح الاستدعاء، وإلا فقيمة غير صفرية إذا كانت قيم type، أو size غير صالحة، أو كان الطلب غير ممكن التنفيذ.

دالة fflush

يُصرّح عن الدالة fflush كما يلي:

#include <stdio.h>

int fflush(FILE *stream);

إذا أشار المجرى stream إلى ملف مفتوح للخرج أو بنمط التحديث، وكان هناك أي بيانات غير مكتوبة فإنها تُكتب خارجًا، وهذا يعني أنه لا يمكن لدالة داخل بيئة مستضافة hosted environment، أو ضمن لغة سي أن تضمن -على سبيل المثال- أن البيانات تصل مباشرةً إلى سطح قرص يدعم الملف. تُهمَل أي عملية ungetc سابقة إذا كان المجرى مرتبطًا بالملف المفتوح بهدف الخرج أو التحديث.

يجب أن تكون آخر عملية على المجرى عملية خرج، وإلا فسنحصل على سلوك غير معرّف.

يتخلص استدعاء fflush الذي يحتوي على وسيط قيمته صفر من جميع مجاري الدخل والخرج، ويجب هنا الانتباه إلى المجاري التي لم تكن عمليتها الأخيرة عملية خرج، أي تفادي حصول السلوك غير المعرّف الذي ذكرناه سابقًا.

تُعيد الدالة القيمة EOF للدلالة على الخطأ، وإلا فالقيمة صفر للدلالة على النجاح.

الدوال عشوائية الوصول Random access functions

تعمل جميع دوال دخل وخرج الملفات بصورةٍ مشابهة بين بعضها، إذ أن الملفات ستُقرأ أو يُكتب إليها بصورةٍ متتابعة إلا إذا اتخذ المستخدم خطوات مقصودة لتغيير موضع مؤشر الملف. ستتسبب عملية قراءة متبوعة بكتابة متبوعة بقراءة ببدء عملية القراءة الثانية بعد نهاية عملية كتابة البيانات فورًا، وذلك بفرض أن الملف مفتوح باستخدام نمط يسمح بهذا النوع من العمليات، كما يجب أن تتذكر أن المجرى stdio يُصرّ على إدخال المستخدم لعملية تحرير ذاكرة مؤقتة بين كل عنصر من عناصر دورة قراءة- كتابة- قراءة، وللتحكم بذلك، تسمح دالة الوصول العشوائي random access function بالتحكم بموضع الكتابة والقراءة ضمن الملف، إذ يُحرّك موضع مؤشر الملف دون الحاجة لقراءة أو كتابة ويشير إلى البايت الذي سيخضع لعملية القراءة أو الكتابة التالية.

هناك ثلاثة أنواع من الدوال التي تسمح بفحص موضع مؤشر الملف أو تغييره، إليك تصاريح كل منهم:

#include <stdio.h>

/* إعادة موضع مؤشر الملف */
long ftell(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);

/* ضبط موضع مؤشر الملف إلى الصفر */
void rewind(FILE *stream);

/* ضبط موضع مؤشر الملف */
int fseek(FILE *stream, long offset, int ptrname);
int fsetpos(FILE *stream, const fpos_t *pos);

تُعيد الدالة ftell القيمة الحالية لموضع مؤشر الملف (المُقاسة بعدد المحارف)، إذا كان المجرى stream يشير إلى ملف ثنائي، وإلا فإنها تعيد رقمًا مميزًا في حالة الملف النصي، ويمكن استخدام هذه القيمة فقط عند استدعاءات لاحقة لدالة fseek لإعادة ضبط موضع مؤشر الملف الحالة. نحصل على القيمة ‎-1L في حالة الخطأ ويُضبط errno.

تضبط الدالة rewind موضع مؤشر الملف الحالي إلى بداية الملف المُشار إليه بالمجرى stream، ويُعاد ضبط مؤشر خطأ الملف باستدعاء الدالة rewind ولا تُعيد الدالة أي قيمة.

تسمح الدالة fseek لموضع مؤشر الملف ضمن المجرى أن يُضبط لقيمة عشوائية (للملفات الثنائية)، أو إلى الموضع الذي نحصل عليه من ftell فقط بالنسبة للملفات النصية، وتتبع الدالة القوانين التالية:

  • يُضبط موضع مؤشر الملف في الحالة الاعتيادية بفارق معين من البايتات (المحارف) عن نقطة الملف المُحددة بالقيمة prtname، وقد يكون الفارق offset سالبًا. قد يأخذ ptrname القيمة SEEK_SET التي تضبط موضع مؤشر الملف نسبيًا إلى بداية الملف، أو القيمة SEEK_CUR التي تضبط موضع مؤشر الملف نسبيًا إلى قيمتها الحالية، أو القيمة SEEK_END التي تضبط موضع مؤشر الملف نسبيًا إلى نهاية الملف، إلا أنه من غير المضمون أن تعمل القيمة الأخيرة بنجاح في المجاري الثنائية.
  • يجب أن تكون قيمة offset في الملفات النصية إما صفر أو قيمة مُعادة بواسطة استدعاء سابق للدالة ftell على المجرى ذاته، ويجب أن تكون قيمة ptrnmae مساويةً إلى SEEK_SET.
  • يُفرغ fseek مؤشر نهاية الملف للمجرى المُحدد ويحذف بيانات أي استدعاء لعملية ungetc، ويعمل ذلك لكلٍّ من الدخل والخرج.
  • تُعاد القيمة صفر للدلالة على النجاح وأي قيمة غير صفرية تدل على طلب ممنوع للدالة.

لاحظ أنه يمكن لكلٍ من ftell و fseek ترميز قيمة موضع مؤشر الملف إلى قيمة من نوع long، وقد لا يحدث هذا بنجاح في حالة استخدامه على الملفات الطويلة جدًا؛ لذلك، يقدم المعيار كلًا من fgetpos و fsetpos للتغلُّب على هذه المشكلة.

تخزِّن الدالة fgetpos موضع مؤشر الملف الحالي ضمن المجرى للكائن المُشار إليه باستخدام المؤشر pos، والقيمة المخزنة هي قيمة مميزة تُستخدم فقط للعودة إلى الموضع المحدد ضمن المجرى ذاته باستخدام الدالة fsetpos. تعمل الدالة fsetpos كما وضحنا سابقًا، كما أنها تُفرغ مؤشر نهاية الملف للمجرى وتُزيل أي تأثير لعمليات ungetc سابقة.

نحصل على القيمة صفر في حالة النجاح لكلا الدالتين، بينما نحصل على قيمة غير صفرية في حالة الخطأ ويُضبط errno.

التعامل مع الأخطاء

تحافظ دوال الدخل والخرج القياسية على مؤشرين لكل مجرى مفتوح للدلالة على نهاية الملف وحالة الخطأ ضمنه، ويمكن الحصول على قيم هذه المؤشرات وضبطها عن طريق الدوال التالية:

#include <stdio.h>

void clearerr(FILE *stream);

int feof(FILE *stream);

int ferror(FILE *stream);

void perror(const char *s);
  • تُفرّغ الدالة clearerr كلًا من مؤشري الخطأ ونهاية الملف EOF للمجرى stream.
  • تُعيد الدالة feof قيمةً غير صفرية إذا كان لمؤشر نهاية الملف الخاص بالمجرى stream قيمة، وإلا فإنها تعيد القيمة صفر.
  • تُعيد الدالة ferror قيمة غير صفرية إذا كان لمؤشر الخطأ الخاص بالمجرى stream قيمة، وإلا فإنها تعيد القيمة صفر.
  • تطبع الدالة perror سطرًا واحدًا يحتوي على رسالة خطأ على خرج البرنامج القياسي مسبوقًا بالسلسلة النصية المُشار إليها بواسطة المؤشر s مع إضافة مسافة فارغة ونقطتين ":". تُحدد رسالة الخطأ بحسب قيمة errno وتُعطي شرحًا بسيطًا عن سبب الخطأ، على سبيل المثال يتسبب البرنامج التالي برسالة خطأ:
#include <stdio.h>
#include <stdlib.h>

main(){

        fclose(stdout);
        if(fgetc(stdout) >= 0){
                fprintf(stderr, "What - no error!\n");
                exit(EXIT_FAILURE);
        }
        perror("fgetc");
        exit(EXIT_SUCCESS);
}

/* رسالة الخطأ */
fgetc: Bad file number

[مثال 2]

لم نقُل أن الرسالة التي سنحصل عليها ستكون واضحة.

ترجمة -وبتصرف- لقسم من الفصل Libraries من كتاب The C Book.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...