EOF ليس رمزًا

قرأت مؤخرًا كتاب "أنظمة الكمبيوتر: العمارة والبرمجة. مظهر المبرمج ". هناك ، في الفصل على نظام يونكس I / O ، ذكر المؤلفون أنه لا يوجد حرف خاص في نهاية الملف EOF. إذا قرأت عن نظام Iix / Unix / Linux ، أو جربته ، إذا كتبت برامج C التي تقرأ البيانات من الملفات ، فمن المحتمل أن يبدو هذا البيان واضحًا لك تمامًا. ولكن دعونا نلقي نظرة فاحصة على العبارتين التاليتين المتعلقتين بما وجدته في الكتاب:





  1. EOF - هذا ليس رمزًا.
  2. لا يوجد حرف خاص في نهاية الملفات.

ما هذا 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".

/* mcat.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 () لمعرفة ما إذا كان يشير إلى أننا وصلنا إلى نهاية الملف:

// mcat.go
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// 1-byte buffer
    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. ولكن ، عند الوصول إلى نهاية الملف ، تتم محاولة قراءة شيء آخر ، يتم رفع حدث التدفق النهائي .

/* mcat.js */
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); /* chunk is one byte */
  }
});

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:

/* syscat.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.

القراء الأعزاء! هل تعرف أي مفاهيم خاطئة أكثر أو أقل انتشارًا من عالم أجهزة الكمبيوتر؟


All Articles