EOF不是符号

最近,我读了《计算机系统:体系结构和程序设计》一书。程序员的样子。” 在Unix I / O系统这一章中,作者提到文件末尾没有特殊字符EOF 如果您阅读或尝试过Unix / Linux I / O系统,或者编写了从文件中读取数据的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-这不是一个符号。

一般而言,什么是符号?符号是文本的最小组成部分。“ A”,“ a”,“ B”,“ b”-所有这些都是不同的符号。字符具有数字代码,在Unicode标准中,该代码称为代码点例如,拉丁字母“ A”以十进制表示的代码为65。可以使用Python解释器的命令行快速检查该字母:

$python
>>> ord('A')
65
>>> chr(65)
'A'

或者,您可以查看Unix / Linux上的ASCII表:

$ 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!.

如您所见,文件的最后一个字符具有一个code 0a从ASCII表中,您可以发现此代码对应于一个字符nl,即换行符。您可以使用Python找出答案:

$ python
>>> chr(0x0a)
'\n'

所以。EOF-这不是符号,文件末尾没有特殊符号。这是EOF什么

什么是EOF?


EOF(文件结束)是在文件读取操作到达末尾的情况下应用程序可以检测到的状态。

让我们看看EOF使用这些语言提供的高级输入输出工具读取文本文件时,如何以不同的编程语言检测状态为此,我们将编写一个非常简单的版本cat,称为mcat它读取ASCII文本字节(字符)并显式检查EOF我们将使用以下语言编写程序:

  • ANSI C
  • Python 3
  • JavaScript(Node.js)

这是带有示例代码存储库。我们继续对其进行分析。

ANSI C


让我们从古老的C开始。这里介绍的程序是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,它将关闭文件并退出。

Python 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+编写的同一示例的简短版本。这里使用了运算符:=(它被称为“海象运算符”或“海象运算符”):

# 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!

JavaScript(Node.js)


Node.js没有用于显式检查的机制EOF但是,当到达文件末尾时,如果尝试读取其他内容,则会引发end stream事件

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

低级系统机制


上面的示例中使用的高级I / O机制如何确定文件的结尾?在Linux上,这些机制直接或间接使用内核提供read()系统调用getc()例如,来自C的函数(或宏)使用系统调用,read()EOFread()指示到达文件末尾的状态时返回。在这种情况下,read()返回0。如果以图表形式描述所有这些内容,则会得到以下信息:


事实证明,该功能getc()基于read()

我们将只使用Unix系统调用编写一个cat命名版本syscat我们这样做不仅是出于兴趣,而且还因为它很可能为我们带来一些好处。

这是用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