قرأت مؤخرًا كتاب "أنظمة الكمبيوتر: العمارة والبرمجة. مظهر المبرمج ". هناك ، في الفصل على نظام يونكس I / O ، ذكر المؤلفون أنه لا يوجد حرف خاص في نهاية الملف EOF
. إذا قرأت عن نظام Iix / Unix / Linux ، أو جربته ، إذا كتبت برامج C التي تقرأ البيانات من الملفات ، فمن المحتمل أن يبدو هذا البيان واضحًا لك تمامًا. ولكن دعونا نلقي نظرة فاحصة على العبارتين التاليتين المتعلقتين بما وجدته في الكتاب:
EOF
- هذا ليس رمزًا.- لا يوجد حرف خاص في نهاية الملفات.
ما هذا EOF
؟EOF ليس رمزًا
لماذا يقول أو يعتقد شخص ما أن EOF
هذا رمز؟ أفترض أن هذا قد يكون كذلك لأنه في بعض برامج C يمكنك العثور على رمز يستخدم التحقق الصريح EOF
لاستخدام الوظائف getchar()
و getc()
.قد يبدو مثل هذا: #include <stdio.h>
...
while ((c = getchar()) != EOF)
putchar(c);
أو هكذا: FILE *fp;
int c;
...
while ((c = getc(fp)) != EOF)
putc(c, stdout);
إذا نظرت إلى المساعدة لـ getchar()
أو getc()
، يمكنك معرفة أن كلتا الوظيفتين تقرأ الحرف التالي من دفق الإدخال. ربما - هذا هو بالضبط ما يسبب الاعتقاد الخاطئ عن الطبيعة EOF
. ولكن هذه مجرد افتراضاتي. دعونا نعود إلى فكرة أن EOF
هذا ليس رمزًا.وما هو الرمز بشكل عام؟ الرمز هو أصغر مكون للنص. "أ" ، "أ" ، "ب" ، "ب" - كل هذه رموز مختلفة. الحرف له كود رقمي ، والذي في معيار Unicode يسمى نقطة رمز . على سبيل المثال ، يحتوي الحرف اللاتيني "A" ، في العلامة العشرية ، على الرمز 65. يمكن التحقق من ذلك بسرعة باستخدام سطر الأوامر لمترجم Python:$python
>>> ord('A')
65
>>> chr(65)
'A'
أو يمكنك إلقاء نظرة على جدول ASCII على Unix / Linux:$ man ascii
سنكتشف أي رمز يتوافق مع EOF
كتابة برنامج صغير في C. في ANSI C ، يتم EOF
تعريف ثابت في stdio.h
، وهو جزء من المكتبة القياسية. عادة ما يكتب على هذا الثابت -1
. يمكنك حفظ الكود التالي في ملف printeof.c
وتجميعه وتشغيله:#include <stdio.h>
int main(int argc, char *argv[])
{
printf("EOF value on my system: %d\n", EOF);
return 0;
}
ترجمة وتشغيل البرنامج:$ gcc -o printeof printeof.c
$ ./printeof
EOF value on my system: -1
لدي هذا البرنامج ، تم اختباره على نظام التشغيل Mac OS وعلى Ubuntu ، تقارير EOF
تساوي -1
. هل يوجد أي رمز بهذا الرمز؟ هنا ، مرة أخرى ، يمكنك التحقق من رموز الأحرف في جدول ASCII ، يمكنك إلقاء نظرة على جدول Unicode ومعرفة النطاق الذي يمكن أن تكون فيه رموز الأحرف. سنتصرف بشكل مختلف: سنبدأ مترجم Python ونستخدم الوظيفة القياسية chr()
لإعطائنا الرمز المطابق للرمز -1
:$ python
>>> chr(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: chr() arg not in range(0x110000)
كما هو متوقع ، الحرف الموجود بالرمز -1
غير موجود. لذا ، في النهاية ، EOF
والحقيقة ليست رمزًا. ننتقل الآن إلى البيان الثاني قيد النظر.لا يوجد حرف خاص في نهاية الملفات.
ربما EOF
- هذه شخصية خاصة يمكن العثور عليها في نهاية الملف؟ أفترض أنك تعرف الإجابة بالفعل. ولكن دعونا نتحقق من افتراضنا بعناية.خذ ملفًا نصيًا بسيطًا ، helloworld.txt ، واعرض محتوياته في تمثيل سداسي عشري. للقيام بذلك ، يمكنك استخدام الأمر xxd
:$ cat helloworld.txt
Hello world!
$ xxd helloworld.txt
00000000: 4865 6c6c 6f20 776f 726c 6421 0a Hello world!.
كما ترى ، فإن الحرف الأخير من الملف لديه رمز 0a
. من جدول ASCII ، يمكنك معرفة أن هذا الرمز يتوافق مع حرف nl
، أي حرف سطر جديد. يمكنك معرفة ذلك باستخدام Python:$ python
>>> chr(0x0a)
'\n'
وبالتالي. EOF
- هذا ليس رمزًا ، وفي نهاية الملفات لا يوجد رمز خاص. ما هذا EOF
؟ما هو EOF؟
EOF
(نهاية الملف) هي حالة يمكن اكتشافها بواسطة التطبيق في حالة تصل فيها عملية قراءة الملف إلى نهايتها.دعونا نلقي نظرة على كيفية اكتشاف الحالة EOF
بلغات برمجة مختلفة عند قراءة ملف نصي باستخدام أدوات الإدخال والإخراج عالية المستوى التي توفرها هذه اللغات. للقيام بذلك ، سنكتب نسخة بسيطة للغاية cat
، والتي سيتم استدعاؤها mcat
. يقرأ بايت نص ASCII (حرف) ويتحقق من صراحة EOF
. سنكتب البرنامج باللغات التالية:- ANSI ج
- بيثون 3
- اذهب
- جافا سكريبت (Node.js)
هنا مستودع مع رمز عينة. نشرع في تحليلهم.ANSI ج
لنبدأ مع ج. الموقر. البرنامج المعروض هنا هو نسخة معدلة cat
من كتاب "لغة البرمجة C".
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
int c;
if ((fp = fopen(*++argv, "r")) == NULL) {
printf("mcat: can't open %s\n", *argv);
return 1;
}
while ((c = getc(fp)) != EOF)
putc(c, stdout);
fclose(fp);
return 0;
}
التحويل البرمجي:$ gcc -o mcat mcat.c
إطلاق:$ ./mcat helloworld.txt
Hello world!
فيما يلي بعض التفسيرات بخصوص الرمز أعلاه:- يفتح البرنامج الملف الذي تم تمريره إليه كوسيطة سطر أوامر.
- تقوم الحلقة
while
بنسخ البيانات من الملف إلى دفق الإخراج القياسي. يتم نسخ البيانات بايت بايت ، وهذا يحدث حتى يتم الوصول إلى نهاية الملف. - عندما يصل البرنامج
EOF
، يغلق الملف ويخرج.
بيثون 3
في Python ، لا توجد آلية للتحقق بشكل صريح EOF
، على غرار تلك المتاحة في ANSI C. ولكن إذا قرأت الملف حرفًا بحرف ، يمكنك الكشف عن الحالة EOF
إذا كان المتغير الذي يخزن الحرف التالي للقراءة فارغًا:# mcat.py
import sys
with open(sys.argv[1]) as fin:
while True:
c = fin.read(1) # 1
if c == '': # EOF
break
print(c, end='')
قم بتشغيل البرنامج وإلقاء نظرة على النتائج التي تم إرجاعها إليه:$ python mcat.py helloworld.txt
Hello world!
هنا نسخة أقصر من نفس المثال مكتوب في Python 3.8+. هنا يتم استخدام عامل التشغيل : = (يطلق عليه "عامل walrus" أو "عامل walrus"):# mcat38.py
import sys
with open(sys.argv[1]) as fin:
while (c := fin.read(1)) != '': # 1 EOF
print(c, end='')
قم بتشغيل هذا الرمز:$ python3.8 mcat38.py helloworld.txt
Hello world!
اذهب
في Go ، يمكنك التحقق صراحة من الخطأ الذي تم إرجاعه بواسطة Read () لمعرفة ما إذا كان يشير إلى أننا وصلنا إلى نهاية الملف:
package main
import (
"fmt"
"os"
"io"
)
func main() {
file, err := os.Open(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "mcat: %v\n", err)
os.Exit(1)
}
buffer := make([]byte, 1)
for {
bytesread, err := file.Read(buffer)
if err == io.EOF {
break
}
fmt.Print(string(buffer[:bytesread]))
}
file.Close()
}
تشغيل البرنامج:$ go run mcat.go helloworld.txt
Hello world!
جافا سكريبت (Node.js)
ليس لدى Node.js آلية للتحقق بشكل صريح من أجل EOF
. ولكن ، عند الوصول إلى نهاية الملف ، تتم محاولة قراءة شيء آخر ، يتم رفع حدث التدفق النهائي .
const fs = require('fs');
const process = require('process');
const fileName = process.argv[2];
var readable = fs.createReadStream(fileName, {
encoding: 'utf8',
fd: null,
});
readable.on('readable', function() {
var chunk;
while ((chunk = readable.read(1)) !== null) {
process.stdout.write(chunk);
}
});
readable.on('end', () => {
console.log('\nEOF: There will be no more data.');
});
تشغيل البرنامج:$ node mcat.js helloworld.txt
Hello world!
EOF: There will be no more data.
آليات النظام منخفضة المستوى
كيف تحدد آليات الإدخال / الإخراج عالية المستوى المستخدمة في الأمثلة أعلاه نهاية الملف؟ في Linux ، تستخدم هذه الآليات بشكل مباشر أو غير مباشر استدعاء النظام read () الذي توفره kernel. دالة (أو ماكرو) getc()
من C ، على سبيل المثال ، تستخدم استدعاء النظام read()
وتعود EOF
إذا كانت read()
تشير إلى حالة الوصول إلى نهاية الملف. في هذه الحالة ، read()
يعود 0
. إذا قمت بتصوير كل هذا في شكل مخطط ، فستحصل على ما يلي:اتضح أن الوظيفة getc()
تقوم على read()
.سنكتب إصدارًا cat
باسم syscat
مكالمات نظام Unix فقط. سنفعل ذلك ليس فقط بدافع الاهتمام ، ولكن أيضًا لأنه قد يجلب لنا بعض الفوائد.هذا البرنامج مكتوب بلغة C:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char c;
fd = open(argv[1], O_RDONLY, 0);
while (read(fd, &c, 1) != 0)
write(STDOUT_FILENO, &c, 1);
return 0;
}
شغلها:$ gcc -o syscat syscat.c
$ ./syscat helloworld.txt
Hello world!
يستخدم هذا الرمز حقيقة أن الدالة read()
، التي تشير إلى الوصول إلى نهاية الملف ، تعود 0
.هذا هو نفس البرنامج المكتوب في Python 3:# syscat.py
import sys
import os
fd = os.open(sys.argv[1], os.O_RDONLY)
while True:
c = os.read(fd, 1)
if not c: # EOF
break
os.write(sys.stdout.fileno(), c)
شغلها:$ python syscat.py helloworld.txt
Hello world!
إليك نفس الشيء المكتوب في Python 3.8+:# syscat38.py
import sys
import os
fd = os.open(sys.argv[1], os.O_RDONLY)
while c := os.read(fd, 1):
os.write(sys.stdout.fileno(), c)
قم بتشغيل هذا الرمز أيضًا:$ python3.8 syscat38.py helloworld.txt
Hello world!
ملخص
EOF
- هذا ليس رمزًا.- لا يوجد حرف خاص في نهاية الملفات.
EOF
- هذه هي الحالة التي أبلغت عنها النواة والتي يمكن اكتشافها بواسطة التطبيق في حالة وصول عملية قراءة البيانات إلى نهاية الملف.- في ANSI C
EOF
، هذا ليس حرفًا مرة أخرى. هذا هو الثابت المحدد stdio.h
الذي تتم فيه كتابة القيمة -1 عادة. EOF
لا يمكن العثور على "حرف" في جدول ASCII أو في Unicode.
القراء الأعزاء! هل تعرف أي مفاهيم خاطئة أكثر أو أقل انتشارًا من عالم أجهزة الكمبيوتر؟