Jim Hall 发布的文章

这里是如何在不使用安装程序的情况下来手动设置你的 FreeDOS 系统。

 title=

大多数的人应该能够使用安装程序来非常容易地安装 FreeDOS 1.3 RC4 。FreeDOS 安装程序会先询问几个问题,然后处理剩余的工作,包括为 FreeDOS 制作安装空间和使系统可启动。

但是,如果安装程序不适合你怎么办?或者,你更喜欢 手动 设置你的 FreeDOS 系统,而不喜欢使用安装程序怎么办?使用 FreeDOS ,你也可以做到这些!让我们在不使用安装程序的情况下逐步走完安装 FreeDOS 的步骤。我将使用 QEMU 虚拟机的一个空白的硬盘驱动器镜像来完成所有的步骤。我使用这个 Linux 命令来创建了一个 100 MB 的硬盘驱动器镜像:

$ qemu-img create freedos.img 100M

我下载了 FreeDOS 1.3 RC4 的 LiveCD ,并将其命名为 FD13LIVE.iso ,它提供了一个 “身临其境” 的环境,我可以在其中运行 FreeDOS ,包括所有的标准工具。大多数用户也使用 LiveCD 自带的常规安装程序来安装 FreeDOS 。但是,在这里我将仅使用 LiveCD ,并从其命令行中使用某些类型的命令来安装 FreeDOS 。

我使用这个相当长的 QEMU 命令来启动虚拟机,并选择 “Use FreeDOS 1.3 in Live Environment mode” 启动菜单项:

$ qemu-system-x86_64 -name FreeDOS -machine pc-i440fx-4.2,accel=kvm,usb=off,dump-guest-core=off -enable-kvm -cpu host -m 8 -overcommit mem-lock=off -no-user-config -nodefaults -rtc base=utc,driftfix=slew -no-hpet -boot menu=on,strict=on -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny -msg timestamp=on -hda freedos.img -cdrom FD13LIVE.iso -device sb16 -device adlib -soundhw pcspk -vga cirrus -display sdl -usbdevice mouse

 title=

选择 "Use FreeDOS 1.3 in Live Environment mode" 来启动 LiveCD(Jim Hall, CC-BY SA 4.0

这个 QEMU 命令行包含大量的选项,乍看可能会让你迷糊。因为你完全使用命令行选项配置 QEMU ,所以在这里有很多东西需要审查。但是,我将简单地重点说明几个重要的选项:

  • -m 8:设置系统存储器(RAM)为 8 MB
  • -boot menu=on,strict=on:使用一个启动菜单,这样,我可以选择从 CD-ROM 镜像或硬盘驱动器镜像启动
  • -hda freedos.img:使用 freedos.img 作为硬盘驱动器镜像
  • -cdrom FD13LIVE.iso:使用 FD13LIVE.iso 作为 CD-ROM 镜像
  • -device sb16 -device adlib -soundhw pcspk:定义计算机带有一个 SoundBlaster16 声卡、AdLib 数字音乐卡、PC 扬声器模拟器(如果你想玩 DOS 游戏的话,这些模拟器很有用)
  • -usbdevice mouse:将用户的鼠标识别为一个 USB 鼠标(在 QEMU 窗口中单击以使用鼠标)

对硬盘驱动器进行分区

你可以从 LiveCD 使用 FreeDOS 1.3 RC4 ,但是,如果你想安装 FreeDOS 到你的计算机中,你需要先在硬盘驱动器上制作安装空间。这需要使用 FDISK 程序来创建一个 分区

从 DOS 命令行中,输入 FDISK 来运行 分区 设置程序。FDISK 是一个全屏交互式程序,你只需要输入数字来选择菜单项。从 FDISK 的主菜单中,输入 1 来在驱动器上创建一个 DOS 分区,然后在接下来的屏幕上输入 1 来创建一个 “主” DOS 分区。

 title=

选择 1 来创建一个分区(Jim Hall, CC-BY SA 4.0

 title=

在接下来的菜单上选择 1 来制作一个主分区(Jim Hall, CC-BY SA 4.0

FDISK 会询问你是否想要使用全部的硬盘空间大小来创建分区。除非你需要在这个硬盘驱动器上和另外一个操作系统(例如 Linux)共享硬盘空间,否则,对于这个提示,你应该回答 Y

FDISK 创建新的分区后,在 DOS 能够识别新的分区信息前,你将需要重新启动 DOS 。像所有的 DOS 操作系统一样,FreeDOS 仅在其启动时识别硬盘驱动器信息。因此,如果你创建或删除任何的磁盘分区的话,你都将需要重新启动 FreeDOS ,只有这样做,FreeDOS 才能识别到更改的分区信息。FDISK 会提醒你重新启动,因此,你是不会忘记的。

 title=

你需要重新启动以识别新的分区(Jim Hall, CC-BY SA 4.0

你可以通过停止或重新启动 QEMU 虚拟机来重新启动 FreeDOS,但是我更喜欢在 FreeDOS 命令行中使用 FreeDOS 的高级电源管理(FDADPM)工具来重新启动 FreeDOS 。为了重新启动,输入命令 FDADPM /WARMBOOT ,FreeDOS 将自动重新启动。

对硬盘驱动器进行格式化

在 FreeDOS 重新启动后,你可以继续设置硬盘驱动器。创建磁盘分区是这个过程的“第一步”;现在你需要在分区上创建一个 DOS 文件系统 ,以便 FreeDOS 可以使用它。

DOS 系统使用字母 AZ 来识别“驱动器”。FreeDOS 将识别第一个硬盘驱动器的第一个分区为 C 驱动器,依此论推。你经常使用字母和一个冒号(:)来表示驱动器,因此我们在上面创建的新分区实际上是 C: 驱动器。

你可以在新的分区上使用 FORMAT 命令来创建一个 DOS 文件系统。这个命令带有一些选项,但是,我们将仅使用 /S 选项来告诉 FORMAT 来使新的文件系统可启动: S 意味着安装 FreeDOS “系统” 文件。输入 FORMAT /S C: 来在 C: 驱动器上制作一个新的 DOS 文件系统。

 title=

格式化分区来创建 DOS 文件系统(Jim Hall, CC-BY SA 4.0

使用 /S 选项,FORMAT 将运行 SYS 程序来传输系统文件。你将看到这是从 FORMAT 输出的一部分:

 title=

FORMAT /S 将使用 SYS 来使磁盘可启动(Jim Hall, CC-BY SA 4.0

安装软件

在使用 FDISK 创建了一个新的分区,并使用 FORMAT 创建了一个新的文件系统后, 新的 C: 驱动器基本上是空的。此时,C: 驱动器仅包含一份内核和 COMMAND.COM 命令行 shell 的副本。为使新的磁盘可以执行一些有用的操作,我们需要在其上安装软件。这是手动安装过程的最后步骤。

FreeDOS 1.3 RC4 LiveCD 包含所有的你可能希望在新的系统上所要安装的软件。每个 FreeDOS 程序都是一个单独的 “软件包” ,它实际上只是一个 Zip 档案文件。建立标准 DOS 环境的软件包存储在 LiveCD 上 PACKAGES 目录下的 BASE 目录之中。

你可以一次一个的将其中的每一个软件包都 “解压缩” 到硬盘驱动器来完成安装。在 Base 组中有 62 个单独的软件包,如果每次安装一个软件包,这可能会花费非常多的时间。不过,你可以运行一个只有一行的 FOR “循环” 命令来 UNZIP 每个程序。接下来 FreeDOS 可以为你 “解压缩” 所有的软件包。

FOR 循环的基本用法中提及的一个单个字母变量(让我们使用 %F),稍后,FreeDOS 将使用该字母变量来 “填充” 文件名称。FOR 还需要括号中的一个文件列表,这个命令会对每个文件都运行一次。用来解压一系列的 Zip 文件的语法看起来像这样:

FOR %F IN (*.ZIP) DO UNZIP %F

这将提取所有的 Zip 文件到当前目录之中。为提取或 UNZIP 文件到一个不同的位置,在 UNZIP 命令行结尾处使用 -d (“目的地”) 选项。对于大多数的 FreeDOS 系统来说,你应该安装软件包到 C:\FDOS 目录中:

 title=

解压缩所有的基本软件包来完成安装 FreeDOS(Jim Hall, CC-BY SA 4.0

FreeDOS 会处理剩余的工作,安装所有的 62 个软件包到你的系统之中。这可能会花费几分钟的时间,因为 DOS 在处理很多单个的文件时会很慢,这个命令需要提取 62 个 Zip 文件。如果我们使用单个的 BASE.ZIP 档案文件的话,安装过程可能会运行地更快,但是使用软件包的话,在你选择想要安装或不安装软件包时会提供更多的灵活性。

 title=

在安装所有的基本软件包后(Jim Hall, CC-BY SA 4.0

在我们安装完所有的东西后,使用 FDADPM /WARMBOOT 来重新启动你的系统。手动安装意味着你的新 FreeDOS 系统没有常见的 FDCONFIG.SYS 配置文件,因此,当 FreeDOS 在启动时,它将假设一些典型的默认值。因为没有 AUTOXEC.BAT 文件,FreeDOS 也会提示你时间和日期。

 title=

在手动安装后,重新启动 FreeDOS(Jim Hall, CC-BY SA 4.0

大多数的用户应该能够使用比较用户友好的过程来在一台新的计算机上安装 FreeDOS 。但是如果你想自己使用“古老的”方法来安装它,那么你可以手动运行安装步骤。这会提供一些额外的灵活性和控制权,因为是你自己安装的一切。现在你知道如何安装它了。


via: https://opensource.com/article/21/6/install-freedos-without-installer

作者:Jim Hall 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

现在,很多嵌入式系统都是在 Linux 上运行的。但是,在很久很久以前,嵌入式系统要么在一个定制的专有的平台上运行,要么在 DOS 上运行。

 title=

FreeDOS 网站 宣称,大多数人使用 FreeDOS 来完成三项主要任务:

  1. 玩经典的 DOS 游戏
  2. 运行老式的 DOS 软件
  3. 运行一款嵌入式系统

但是,运行一个“嵌入式”系统的意义是什么呢?

嵌入式系统基本上是一款非常小的系统,专用于运行一个特定的任务。你可以把现在的嵌入式系统当作是 物联网(IoT)的一部分,这包括传感器、恒温器和门铃摄像头。现在,很多嵌入式系统都是在 Linux 上运行的。

但是,在很久很久以前,嵌入式系统要么在一个定制的专有的平台上运行,要么在 DOS 系统上运行。在现在,一些基于 DOS 的嵌入式系统仍然在运行,例如,收银机或电话专用交换机(PBX)系统。举个例子来说,在 2017 年,酷爱列车的人发现一个正在运行 FreeDOS 的俄罗斯的电动列车控制系统 (俄语: САВПЭ),它使用特殊的软件来控制和监控郊区列车的线路,并发布乘客通告。

在 DOS 上建立一个嵌入式系统需要定义一个最小化的 DOS 环境来运行单个应用程序。幸运的是,设置一个最小化的 FreeDOS 环境是非常容易的。从技术上来说,启动 FreeDOS 并运行 DOS 应用程序仅需要内核和一个 FDCONFIG.SYS 配置文件。

安装一款最小化的系统

我们可以使用 QEMU 仿真器来模拟一个专用的、最小化的 FreeDOS 系统,并给它分配很少的资源。为了更准确地反映一个嵌入式系统,我将定义一个只有 8 MB 的存储器和仅仅有 2 MB 的硬盘驱动器的虚拟机。

为创建这个微小的虚拟硬盘,我将使用这个 qemu-img 命令来定义一个 2M 的文件:

$ qemu-img create tiny.img 2M
Formatting 'tiny.img', fmt=raw size=2097152

下面的这行命令定义了一个 32 位的 “i386” CPU、8MB 的存储器,使用 2MB 的 tiny.img 文件作为硬盘驱动器镜像,使用 FreeDOS 1.3 RC4 LiveCD 作为 CD-ROM 介质。我们也将机器设置为从 CD-ROM 驱动器启动(-boot order=d),尽管我们只需要用它来安装系统。在我们完成所有的设置后,我们将从该硬盘启动完成的嵌入式系统:

qemu-system-i386 -m 8 -hda tiny.img -cdrom FD13LIVE.iso -boot order=d

使用 “ 现场环境模式 Live Environment mode ” 来启动系统,这将为我们提供一个正在运行的 FreeDOS 系统,我们可以使用它来将一个最小化的 FreeDOS 转移到硬盘上。

 title=

启动到 LiveCD 环境之中(Jim Hall, CC-BY SA 4.0

我们需要在虚拟硬盘驱动器上为我们的程序创建一个分区。为此,从命令行中运行 FDISK 程序。FDISK 是 FreeDOS 上的一个标准的 磁盘分区 实用程序。使用 FDISK 来创建一个单个硬盘驱动器分区,占用整个(2 MB)硬盘驱动器。

 title=

FDISK,在创建 2 MB 分区后(Jim Hall, CC-BY SA 4.0

但是,在你重新启动 FreeDOS 之前,FreeDOS 不会看到新的硬盘驱动器分区 — FreeDOS 仅在启动时读取硬盘详细信息。退出 FDISK ,并重新启动 FreeDOS 。

在重新启动后,你需要在新的硬盘驱动器上创建一个 DOS 文件系统。因为这里只有一个虚拟硬盘,FreeDOS 将识别其为 C: 驱动器。你可以使用 FORMAT 命令来在 C: 驱动器上创建一个 DOS 文件系统。使用 /S 选项将把操作系统文件(内核,外加一个 COMMAND.COM shell 的副本)转移到新的驱动器上。

 title=

格式化新的驱动器来创建一个 DOS 文件系统(Jim Hall, CC-BY SA 4.0 你已经创建了硬盘驱动器并将其格式化,现在,你可以安装应用程序,这些应用程序是将会在新安装的嵌入式系统上运行的。

安装专用的应用程序

嵌入式系统实际上只是一个运行在一个专用系统上的单一用途的应用程序。这些应用程序通常是为其将要控制的系统而自定义构建的,例如,一台收银机、显示终端、或控制环境。在这个演示中,让我们使用一个来自 FreeDOS 1.3 RC4 安装光盘中的程序。它需要足够小,以适应我们为其创建的 2 MB 微型硬盘驱动器。这可以是任何东西,所以,为了好玩,让我们把它变成一个游戏。

FreeDOS 1.3 RC4 包含一些有趣的游戏。我喜欢的一个游戏是一个名称为 “Simple Senet” 的棋类游戏。它是一个基于 Senet 的古埃及棋类游戏。游戏的细节对这个演示并不重要,我们将安装它,并将其设置为嵌入式系统的专业应用程序。

为安装应用程序,在 FreeDOS 1.3 RC4 LiveCD 上,进入 \PACKAGES\GAMES 目录。你将在其中看到一个很长的软件包列表,而我们想要的 SENET.ZIP

 title=

来自 FreeDOS 1.3 RC4 的一个游戏软件包列表(Jim Hall, CC-BY SA 4.0

为解压缩 “Simple Senet” 软件包到虚拟硬盘上,使用 UNZIP 命令。所有的 FreeDOS 软件包都是 Zip 文件,因此,你可以使用任意与 Zip 兼容的档案实用程序来管理它们。FreeeDOS 1.3 RC4 包含创建 Zip 档案文件的 ZIP 和提取 Zip 档案文件的 UNZIP 。它们都来自 Info-Zip 项目

UNZIP SENET.ZIP -d C:\FDOS

通常,使用 UNZIP 来提取 Zip 文件到当前目录中。在命令行结尾的 -d C:\FDOS 选项将告诉 UNZIP 来提取 Zip 文件到 C:\FDOS 目录之中。(-d 指的是“目的地”)

 title=

解压缩 Simple Senet 游戏(Jim Hall, CC-BY SA 4.0

为了让嵌入式系统启动时运行 “Simple Senet” 游戏,我们需要告诉 FreeDOS 来使用 Senet 作为系统的 “shell” 。 默认的 FreeDOS 的 shell 是 COMMAND.COM 程序,但是,你可以在 FDCONFIG.SYS 内核配置文件中使用 SHELL= 指令来定义一个不同的 shell 程序。我们可以使用 FreeDOS 的 Edit 来创建新的 C:\FDCONFIG.SYS 文件。

 title=

(Jim Hall, CC-BY SA 4.0

如果你需要定义其它的参数来支持嵌入式系统,你可以将其添加到 FDCONFIG.SYS 文件之中。例如,你可能需要使用 SET 动作来设置环境变量,或者使用 FILES=BUFFERS= 语句来调整 FreeDOS 内核。

运行嵌入式系统

在全面地完成嵌入式系统的定义之后,现在,我们可以重新启动计算机来运行嵌入式应用程序。运行一个嵌入式系统通常仅需要有限的资源,因此,在这个演示中,我们需要调整 QEMU 命令行来只从硬盘驱动器(-boot order=c)中启动,而不再定义一个 CD-ROM 驱动器:

qemu-system-i386 -m 8 -hda tiny.img -boot order=c

当 FreeDOS 内核启动时,它将读取 FDCONFIG.SYS 文件以获取启动参数。然后,它将使用 SHELL= 行的定义来运行 shell 。这将自动地运行 “Simple Senet” 游戏。

 title=

作为一个嵌入式系统运行 Simple Senet(Jim Hall, CC-BY SA 4.0

我们已经使用了 “Simple Senet” 来演示如何在 FreeDOS 上设置一个嵌入式系统。根据你的需要,你可以使用任何你喜欢的独立应用程序。在 FDCONFIG.SYS 中使用 SHELL= 行将其定义为 DOS 的 shell ,FreeDOS 将在启动时自动地启动该应用程序。

不过,在这里有一个限制。嵌入式系统通常不需要退回到一个命令行提示符之中,因此这些专用应用程序通常不允许用户退出到 DOS 之中。如果你设法退出了嵌入式应用程序,你可能会看到一个 “Bad or missing Command Interpreter” 的提示,你将需要在其中输入一个新的 shell 的完整路径。对于一个以用户为中心的桌面系统来说,这将是一个问题。但是在一个嵌入式系统上,它只专注执行一种工作的,那么,你也永远不需要退出嵌入式应用程序。


via: https://opensource.com/article/21/6/freedos-embedded-system

作者:Jim Hall 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

环境变量几乎在每种命令行环境中都是很有帮助的,自然包括 FreeDOS 。

几乎在每个命令行环境中的一个有用的功能是 环境变量。其中的一些变量允许你控制命令行的行为或功能,其它的变量仅允许你存储可能稍后需要的数据,在 FreeDOS 中也使用了环境变量。

在 Linux 上的变量

在 Linux 上,你可能已经熟悉其中的一些重要的环境变量。在 Linux 上的 Bash shell 中,PATH 变量标示着 shell 可以在哪里找到程序和命令。例如,在我的 Linux 系统上,我的 PATH 值如下:

bash$ echo $PATH
/home/jhall/bin:/usr/lib64/ccache:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin

这意味着,当我输入一个像 cat 这样的命令的名称时,Bash 将会按顺序检查我在 PATH 变量中所列出的每个目录:

  1. /home/jhall/bin
  2. /usr/lib64/ccache
  3. /usr/local/bin
  4. /usr/local/sbin
  5. /usr/bin
  6. /usr/sbin

在我的实例中,cat 命令位于 /usr/bin 目录,因此,完整的路径是 /usr/bin/cat

为在 Linux 上设置一个环境变量,你可以输入一个变量的名称,接着输入一个等于符号(=),接着输入一个要存储在变量中的值。为了随后使用 Bash 引用这个值,你需要在变量的名称前输入一个美元符号($)。

bash$ var=Hello
bash$ echo $var
Hello

在 FreeDOS 上的变量

在 FreeDOS 上,环境变量提供一种类似的功能。一些变量控制 DOS 系统的行为,另一些变量用于存储一些临时值。

为在 FreeDOS 上设置一个环境变量,你需要使用 SET 关键字。FreeDOS 是 不区分大小写的 ,因此你可以输入大写字母也可以使用小写字母。接下来,像你在 Linux 上一样设置变量,使用变量名称,一个等于符号(=),你想要存储的值。

不过,在 FreeDOS 中引用或 扩展 一个环境变量的值的方法,与你在 Linux 上所使用的方法是完全不同的。在 FreeDOS 中,你不能使用美元符号($)来引用一个变量。你反而需要使用百分符号 (%)来包围变量的名称。

Use % (not $) to reference a variable's value

在名称前后使用百分符号是非常重要,因为这就是 FreeDOS 知悉变量名称在哪里开始和结束的方式。这是非常有用的,因为它会允许你引用一个变量的值,与此同时,它会立即附加(或预置)其它的文本到值中。让我通过设置一个新的名称为 reply 的值为 yes 的变量,然后在 “11” 之前和 “22” 之后引用这个值来演示这一点:

Set and reference an environment variable

因为 FreeDOS 是不区分大小写的,所以你可以使用大写字母称或小写字母的变量名称以及 SET 关键字。不过,变量的值将使用你在命令行中所输入的字母。

最后,你可以看到当前在 FreeDOS 中定义的所有的环境变量。不使用任何参数的 SET 关键字将显示所有的变量,因此你可以一目了然:

Show all variables at once with SET

环境变量是一个有用的基本的命令行环境,同样适用于 FreeDOS 。你可以设置你自己的变量以满足你自己的需要,但是要仔细地更改 FreeDOS 使用的一些变量。这些变量会更改你正在运行的 FreeDOS 系统的行为:

  • DOSDIR:FreeDOS 安装目录的位置,通常是 C:\FDOS
  • COMSPEC:FreeDOS 的 shell 的当前实例,通常是 C:\COMMAND.COM%DOSDIR%\BIN\COMMAND.COM
  • LANG:用户的首选语言
  • NLSPATH:系统语言文件的位置,通常是 %DOSDIR%\NLS
  • TZ:系统的时区
  • PATH:一个目录列表,FreeDOS 可以在其中找到要运行的程序,例如 %DOSDIR%\BIN
  • HELPPATH:系统文档文件的位置,通常是 %DOSDIR%\HELP
  • TEMP:一个临时目录,FreeDOS 在其中存储来自每个命令的输出,如同它在命令行上的程序之间的 “管道” 数据
  • DIRCMD:一个控制 DIR 命令如何显示文件和目录的变量,通常设置 /OGNE 来排序(O)内容,先通过分组(G)目录,接下来按照名称(N) 、扩展名(E)来排序条目

如果你偶然间更改了任意的 FreeDOS 的 “内部” 变量,你可能会阻碍 FreeDOS 的一些部分的正常工作。在这种情况下,只需要简单地重新启动你的计算机,FreeDOS 将会按照系统默认值重新设置变量。


via: https://opensource.com/article/21/6/freedos-environment-variables

作者:Jim Hall 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

FreeDOS 下批处理文件的实用指南。

 title=

即使你以前没有使用过 DOS,你也可能知道它的命令行 shell,即 COMMAND.COM。它已经成为 DOS 的同义词,FreeDOS 为此也实现了一个类似的 shell,称为 “FreeCOM”,但也命名为 COMMAND.COM,就像在其他 DOS 系统上一样。

但是 FreeCOM shell 可以做的不仅仅是为你提供一个命令行提示符让你在其中运行命令,如果你需要在 FreeDOS 上自动执行任务,你可以使用 批处理文件,也称为 “BAT 文件”,因为这些脚本使用 .BAT 扩展名。

批处理文件可能比你在 Linux 编写的脚本要简单得多。因为在很久以前,这个功能最初被添加到 DOS 时,它是为了让 DOS 用户“批量处理”某些命令。它的条件分支没有太大的灵活性,也不支持更高级的功能,例如算术扩展、标准输出和错误消息的重定向、后台进程、测试、循环(这项支持)和 Linux 脚本中常见的其他结构。

本文是 FreeDOS 下批处理文件的实用指南。记住通过用百分号(%)包裹变量名称来引用环境变量,例如 %PATH%。但是,请注意,由于历史原因,FOR 循环的构造略有不同。

打印输出

批处理文件可能需要向用户打印消息,让用户知道发生了什么。使用 ECHO 语句打印消息。例如,一个批处理文件可能使用以下语句表明它已完成了任务:

ECHO Done

ECHO 语句不需要引号。FreeCOM ECHO 语句不会以任何特殊方式处理引号,它会像普通文本一样打印它们。

通常,FreeDOS 在执行批处理文件时会打印每一行。这在一个非常短的批处理文件中通常不是问题,它只为用户定义了几个环境变量。但是对于执行更多工作的较长批处理文件而言,批处理行的这种一直显示可能会变得很麻烦。要阻止此输出,在 ECHO 语句中使用 OFF 关键字,如下所示:

ECHO OFF

使用 ON 关键字在 FreeDOS 运行时恢复显示批处理行。

ECHO ON

大多数批处理文件在第一行包含一个 ECHO OFF 语句,以阻止消息,但是 shell 在执行语句时仍然会在屏幕上打印 ECHO OFF。为了隐藏该语句,批处理文件通常在前面使用 @ 符号。这样,任何以这个特殊字符开头的行都不会打印,即使打开了 ECHO

@ECHO OFF

注释

编写较长批处理文件时,大多数程序员都喜欢使用 注释 来提醒自己这个批处理文件的用途。在批处理文件中注释,使用 REM(remark)关键字。REM 之后的任何内容都会被 FreeCOM shell 忽略。

@ECHO OFF
REM This is a comment

执行“辅助”批处理文件

通常,FreeCOM 一次只运行一个批处理文件。但是,你可能需要使用另一个批处理文件来执行其他操作,例如为多个批处理文件设置公共环境变量。

如果你从"正在运行"的批处理文件中直接调用第二个批处理文件,FreeCOM 将完全切换到第二个批处理文件,并停止处理第一个。要改为在第一个批处理文件“内部”运行第二个批处理文件,你需要告诉 FreeDOS shell 使用 CALL 关键字去 调用 第二个批处理文件。

@ECHO OFF
CALL SETENV.BAT

条件分支

批处理文件确实支持使用 IF 语句的简单条件分支。它有三种基本形式:

  1. 测试上一条命令的返回状态
  2. 测试一个变量是否等于一个值
  3. 测试文件是否存在

IF 语句的一个常见用途是测试程序是否成功返回。如果它们正常运行,大多数程序将返回零值,或者在出现错误时返回一些其他值。在 DOS 中,这称为 错误级别,这是 IF 测试的特例。

测试名为 MYPROG 的程序是否成功退出,实际上是检查程序是否返回“零”。使用 ERRORLEVEL 关键字来测试特定值。例如:

@ECHO OFF
MYPROG
IF ERRORLEVEL 0 ECHO Success

使用 ERRORLEVEL 测试错误级别是检查程序退出状态的笨拙方法。检查 DOS 程序的不同返回值,更有用的方法是使用 FreeDOS 为你定义的特殊变量,称为 ERRORLEVEL。它存储了最近执行程序的错误级别,然后你可以使用 == 测试不同的值。

你可以使用 ==IF 语句来测试变量是否等于某个值。就像一些编程语言,你可以使用 == 直接比较两个值。通常,在一侧引用一个环境变量,在另一侧引用一个值,但你也可以比较两个变量的值以查看它们是否相同。例如,你可以使用此批处理文件重写上面的 ERRORLEVEL 代码:

@ECHO OFF
MYPROG
IF %ERRORLEVEL%==0 ECHO Success

IF 语句的另一个常见用途是测试文件是否存在,如果存在则采取操作。你可以使用 EXIST 关键字来测试。例如,要删除名为 TEMP.DAT 的临时文件,你可以在批处理文件中使用以下行:

@ECHO OFF
IF EXIST TEMP.DAT DEL TEMP.DAT

对于任何 IF 语句,你都可以使用 NOT 关键字来 否定 测试。在文件 存在时打印消息,你可以这样写:

@ECHO OFF
IF NOT EXIST TEMP.DAT ECHO No file

分支执行

利用 IF 测试的一种方法是跳转到批处理文件中完全不同的部分,这取决于 IF 测试的结果。在最简单的情况下,如果一个关键命令失败,你可能希望跳到批处理文件的末尾。或者,如果某些环境变量设置不正确,你可能想要执行其他语句。

你可以使用 GOTO 指令跳转到批处理文件的其他部分。它会跳转到批处理文件中称为 标签 的特定行。注意,这是一个严格的 “go-to” 跳转:批处理文件执行将在新标签处启动。

假设程序需要一个现有的空文件来存储临时数据,如果文件不存在,则需要在运行程序之前创建一个文件。你可以将这些动作添加到批处理文件中,这样你的程序始终有一个临时文件可供使用:

@ECHO OFF
IF EXIST temp.dat GOTO prog
ECHO Creating temp file...
TOUCH temp.dat
:prog
ECHO Running the program...
MYPROG

当然,这是一个非常简单的例子。对于这种情况,你可以重写批处理文件,将创建临时文件作为 IF 语句的一部分:

@ECHO OFF
IF NOT EXIST temp.dat TOUCH temp.dat
ECHO Running the program...
MYPROG

迭代

如果你需要对一组文件执行相同的任务怎么办?你可以使用 FOR 循环 迭代 一组文件。这是一个单行循环,每次使用不同的文件运行单个命令。

FOR 循环对迭代变量使用一种特殊的语法,它的用法与其他 DOS 环境变量不同。要循环编辑一组文本文件,可以使用以下语句:(LCTT 译注:原文此处写错了,少写了一个 %

@ECHO OFF
FOR %%F IN (*.TXT) DO EDIT %%F

注意,如果在命令行中运行此循环,而不是在批处理文件中,那么迭代变量仅需要指定一个百分号(%):

C:\> FOR %F IN (*.TXT) DO EDIT %F

命令行处理

在运行批处理文件时,FreeDOS 提供了一种简单的方法来检测用户可能提供的命令行选项。FreeDOS 解析命令行输入,并将前九个选项存储在特殊变量 %1%2 ..... 等中,直到 %9。注意,无法通过这种方式直接访问第十一个(及之后)选项。特殊变量 %0 存储批处理文件的名称。

如果你的批处理文件需要处理 9 个以上的选项,你可以使用 SHIFT 语句移除第一个选项,并将每个选项向下 移动 一个值。所以第二个选项变成了 %1,第十个选项变成了 %9

大多数批处理文件只需要移动一个值。但是,如果你需要以其他增量进行移位,可以将参数提供给 SHIFT 语句。例如:

SHIFT 2

下面是一个简单的批处理文件,演示了移位操作:

@ECHO OFF
ECHO %1 %2 %3 %4 %5 %6 %7 %8 %9
ECHO Shift by one ..
SHIFT 1
ECHO %1 %2 %3 %4 %5 %6 %7 %8 %9

执行带有十个选项的批处理文件显示了 SHIFT 语句如何重新排列命令行选项,因此批处理文件现在可以用 %9 访问第十个参数:

C:\SRC>args 1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9
Shift by one ..
2 3 4 5 6 7 8 9 10
C:\SRC>

via: https://opensource.com/article/21/6/automate-tasks-bat-files-freedos

作者:Jim Hall 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

使用 Linux 或 FreeDOS 从一个 C 程序中生成彩色的 ASCII 艺术。

 title=

利用扩展 ASCII 字符集和它的绘画元素集合的全彩 ASCII 艺术在 DOS 上曾经相当流行。你可以在你的下一个 FreeDOS 程序中加入 ASCII 艺术,作为一个很酷的“欢迎”屏幕,或者作为一个提供了更多程序信息的彩色“退出”屏幕,来增加一点视觉上的乐趣。

但是,这种 ASCII 艺术的风格并不仅仅局限于 FreeDOS 程序。你可以在 Linux 终端模式的程序中使用同样的方法。虽然 Linux 使用 ncurses 来控制屏幕,而不是 DOS 的 conio,但相关的概念也适用于 Linux 程序。本文探讨了如何从 C 语言程序中生成彩色 ASCII 艺术。

ASCII 艺术文件

你可以使用各种工具来绘制你的 ASCII 艺术。在这个例子中,我使用了一个叫做 TheDraw 的老式 DOS 应用程序,但是你可以在 Linux 上找到现代的开源 ASCII 艺术程序,比如 Moebius(Apache 许可证)或者 PabloDraw(MIT 许可证)。只要你知道保存的数据是什么样子的,你使用什么工具并不重要。

下面是一个 ASCII 艺术文件样本的一部分,以 C 源代码保存。请注意,这个代码片段定义了几个值。IMAGEDATA_WIDTHIMAGEDATA_DEPTH 定义了屏幕上的列数和行数。在这里,它是一个 80x25 的 ASCII 艺术“图像”。IMAGEDATA_LENGTH 定义了 IMAGEDATA 数组中的条目数量。ASCII 艺术画面中的每个字符可以用两个字节的数据表示。要显示的字符和包含该字符的前景和背景颜色的颜色属性。对于一个 80x25 的屏幕,每个字符都与一个属性配对,该数组包含 4000 个条目(即 80*25*2=4000)。

#define IMAGEDATA_WIDTH 80
#define IMAGEDATA_DEPTH 25
#define IMAGEDATA_LENGTH 4000
unsigned char IMAGEDATA [] = {
    '.', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,
    ' ', 0x08,  ' ', 0x08,  '.', 0x0F,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,
    ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  '.', 0x0F,
    ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,
    ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,  ' ', 0x08,

数组的其它部分依此类推。

为了在屏幕上显示这种 ASCII 艺术,你需要写一个小小的程序来读取数组并以正确的颜色打印每个字符。

设置一个颜色属性

这个 ASCII 艺术文件中的颜色属性在一个字节中定义了背景和前景的颜色,用十六进制的值表示,如 0x080x6E。十六进制是适合表达这样的颜色“对”的紧凑方式。

像 Linux 上的 ncurses 或 DOS 上的 conio 这样的字符模式系统 只能显示 16 种颜色。这就是十六种可能的文本颜色和八种背景颜色。用二进制计算十六个值(从 0 到 15)只需要四个二进制位。

1111 是二进制的 15

而且方便的是,十六进制可以用一个字符表示 0 到 15:0123456789ABCDEF。所以十六进制的值 F 是数字 15,或二进制的 1111

通过颜色对,你可以用一个八位的字节来编码背景和前景的颜色。这就是文本颜色的四个二进制位(十六进制中的 0 到 15 或 0 到 F)和背景颜色的三个二进制位(十六进制中的 0 到 7 或 0 到 E)。字节中剩余的二进制位在这里没有使用,所以我们可以忽略它。

为了将颜色对或属性转换成你的程序可以使用的颜色值,你需要 使用位掩码,只指定用于文字颜色或背景颜色的位。使用 FreeDOS 上的 OpenWatcom C 编译器,你可以编写这个函数,从颜色属性中适当地设置颜色。

void
textattr(int newattr)
{
  _settextcolor(newattr & 15);         /* 0000xxxx */
  _setbkcolor((newattr >> 4) & 7);     /* 0xxx0000 */
}

_settextcolor 函数只设置文本颜色,_setbkcolor 函数设置背景颜色。两者都定义在 graph.h 中。注意,由于颜色属性在一个字节值中包括了背景色和前景色,textattr 函数使用 &(二进制的“与”运算)来设置一个位掩码,只隔离了属性中的最后四个位。这就是颜色对存储前景颜色的值 0 到 15 的地方。

为了得到背景色,该函数首先执行了一个位移,将位“推”到右边。这就把“上”位放到了“下”位范围,所以任何像 0xxx0000 这样的位都变成了 00000xxx。我们可以用另一个的位掩码 7(二进制 0111)来挑选出背景颜色值。

显示 ASCII 艺术

IMAGEDATA 数组包含整个 ASCII 艺术屏幕和每个字符的颜色值。为了在屏幕上显示 ASCII 艺术,你的程序需要扫描该数组,设置颜色属性,然后一次在屏幕上显示一个字符。

让我们在屏幕的底部留出空间,以便向用户提供单独的信息或提示。也就是说,我不想显示一个 80 列 ASCII 屏幕的所有 25 行,而只想显示前 24 行。

  /* print one line less than the 80x25 that's in there:
     80 x 24 x 2 = 3840 */

  for (pos = 0; pos < 3840; pos += 2) {
...
  }

for 循环里面,我们需要设置颜色,然后打印字符。OpenWatcom C 编译器提供了一个函数 _outtext 来显示带有当前颜色值的文本。然而,这需要传递一个字符串,如果我们需要一个一个地处理每个字符,在一行中的每个字符需要不同颜色的情况下,效率就会很低。

相反,OpenWatcom 有一个类似的函数,叫做 _outmem,允许你指示要显示多少个字符。对于一次一个字符,我们可以在 IMAGEDATA 数组中提供一个字符值的指针,并告诉 _outtext 只显示一个字符。这将使用当前的颜色属性显示该字符,这就是我们需要的。

  for (pos = 0; pos < 3840; pos += 2) {
    ch = &IMAGEDATA[pos];              /* pointer assignment */
    attr = IMAGEDATA[pos + 1];
 
    textattr(attr);
    _outmem(ch, 1);
  }

这个更新的 for 循环通过向 IMAGEDATA 数组分配一个指针来设置字符 ch。接下来, 循环设置文本属性, 然后用 _outmem 显示字符.

整合起来

有了 textattr 函数和处理数组的 for 循环, 我们可以编写一个完整的程序来显示 ASCII 艺术文件的内容。对于这个例子,将 ASCII 艺术文件保存为 imgdata.inc,并用 #include 语句将其包含在源文件中。

#include <stdio.h>
#include <conio.h>
#include <graph.h>

#include "imgdata.inc"

void
textattr(int newattr)
{
  _settextcolor(newattr & 15);         /* 0000xxxx */
  _setbkcolor((newattr >> 4) & 7);     /* 0xxx0000 */
}

int
main()
{
  char *ch;
  int attr;
  int pos;

  if (_setvideomode(_TEXTC80) == 0) {
    fputs("Error setting video mode", stderr);
    return 1;
  }

  /* draw the array */

  _settextposition(1, 1);              /* top left */

  /* print one line less than the 80x25 that's in there:
     80 x 24 x 2 = 3840 */

  for (pos = 0; pos < 3840; pos += 2) {
    ch = &IMAGEDATA[pos];              /* pointer assignment */
    attr = IMAGEDATA[pos + 1];

    textattr(attr);
    _outmem(ch, 1);
  }

  /* done */

  _settextposition(25, 1);             /* bottom left */

  textattr(0x0f);
  _outtext("Press any key to quit");

  getch();

  textattr(0x00);
  return 0;
}

在 FreeDOS 上使用 OpenWatcom C 编译器编译该程序,你会得到一个显示这个节日信息的新程序。

ASCII艺术中的万圣节信息

万圣节快乐(Jim Hall, CC-BY-SA 4.0)

万圣节快乐,各位!


via: https://opensource.com/article/21/10/ascii-linux-halloween

作者:Jim Hall 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

增强 C 语言程序的弹性和可靠性的五种方法。

 title=

即使是最好的程序员也无法完全避免错误。这些错误可能会引入安全漏洞、导致程序崩溃或产生意外操作,具体影响要取决于程序的运行逻辑。

C 语言有时名声不太好,因为它不像近期的编程语言(比如 Rust)那样具有内存安全性。但是通过额外的代码,一些最常见和严重的 C 语言错误是可以避免的。下文讲解了可能影响应用程序的五个错误以及避免它们的方法:

1、未初始化的变量

程序启动时,系统会为其分配一块内存以供存储数据。这意味着程序启动时,变量将获得内存中的一个随机值。

有些编程环境会在程序启动时特意将内存“清零”,因此每个变量都得以有初始的零值。程序中的变量都以零值作为初始值,听上去是很不错的。但是在 C 编程规范中,系统并不会初始化变量。

看一下这个使用了若干变量和两个数组的示例程序:

#include <stdio.h>
#include <stdlib.h>

int
main()
{
  int i, j, k;
  int numbers[5];
  int *array;

  puts("These variables are not initialized:");

  printf("  i = %d\n", i);
  printf("  j = %d\n", j);
  printf("  k = %d\n", k);

  puts("This array is not initialized:");

  for (i = 0; i < 5; i++) {
    printf("  numbers[%d] = %d\n", i, numbers[i]);
  }

  puts("malloc an array ...");
  array = malloc(sizeof(int) * 5);

  if (array) {
    puts("This malloc'ed array is not initialized:");

    for (i = 0; i < 5; i++) {
      printf("  array[%d] = %d\n", i, array[i]);
    }

    free(array);
  }

  /* done */

  puts("Ok");
  return 0;
}

这个程序不会初始化变量,所以变量以系统内存中的随机值作为初始值。在我的 Linux 系统上编译和运行这个程序,会看到一些变量恰巧有“零”值,但其他变量并没有:

These variables are not initialized:
  i = 0
  j = 0
  k = 32766
This array is not initialized:
  numbers[0] = 0
  numbers[1] = 0
  numbers[2] = 4199024
  numbers[3] = 0
  numbers[4] = 0
malloc an array ...
This malloc'ed array is not initialized:
  array[0] = 0
  array[1] = 0
  array[2] = 0
  array[3] = 0
  array[4] = 0
Ok

很幸运,ij 变量是从零值开始的,但 k 的起始值为 32766。在 numbers 数组中,大多数元素也恰好从零值开始,只有第三个元素的初始值为 4199024。

在不同的系统上编译相同的程序,可以进一步显示未初始化变量的危险性。不要误以为“全世界都在运行 Linux”,你的程序很可能某天在其他平台上运行。例如,下面是在 FreeDOS 上运行相同程序的结果:

These variables are not initialized:
  i = 0
  j = 1074
  k = 3120
This array is not initialized:
  numbers[0] = 3106
  numbers[1] = 1224
  numbers[2] = 784
  numbers[3] = 2926
  numbers[4] = 1224
malloc an array ...
This malloc'ed array is not initialized:
  array[0] = 3136
  array[1] = 3136
  array[2] = 14499
  array[3] = -5886
  array[4] = 219
Ok

永远都要记得初始化程序的变量。如果你想让变量将以零值作为初始值,请额外添加代码将零分配给该变量。预先编好这些额外的代码,这会有助于减少日后让人头疼的调试过程。

2、数组越界

C 语言中,数组索引从零开始。这意味着对于长度为 10 的数组,索引是从 0 到 9;长度为 1000 的数组,索引则是从 0 到 999。

程序员有时会忘记这一点,他们从索引 1 开始引用数组,产生了 “大小差一” off by one 错误。在长度为 5 的数组中,程序员在索引“5”处使用的值,实际上并不是数组的第 5 个元素。相反,它是内存中的一些其他值,根本与此数组无关。

这是一个数组越界的示例程序。该程序使用了一个只含有 5 个元素的数组,但却引用了该范围之外的数组元素:

#include <stdio.h>
#include <stdlib.h>

int
main()
{
  int i;
  int numbers[5];
  int *array;

  /* test 1 */

  puts("This array has five elements (0 to 4)");

  /* initalize the array */
  for (i = 0; i < 5; i++) {
    numbers[i] = i;
  }

  /* oops, this goes beyond the array bounds: */
  for (i = 0; i < 10; i++) {
    printf("  numbers[%d] = %d\n", i, numbers[i]);
  }

  /* test 2 */

  puts("malloc an array ...");

  array = malloc(sizeof(int) * 5);

  if (array) {
    puts("This malloc'ed array also has five elements (0 to 4)");

    /* initalize the array */
    for (i = 0; i < 5; i++) {
      array[i] = i;
    }

    /* oops, this goes beyond the array bounds: */
    for (i = 0; i < 10; i++) {
      printf("  array[%d] = %d\n", i, array[i]);
    }

    free(array);
  }

  /* done */

  puts("Ok");
  return 0;
}

可以看到,程序初始化了数组的所有值(从索引 0 到 4),然后从索引 0 开始读取,结尾是索引 9 而不是索引 4。前五个值是正确的,再后面的值会让你不知所以:

This array has five elements (0 to 4)
  numbers[0] = 0
  numbers[1] = 1
  numbers[2] = 2
  numbers[3] = 3
  numbers[4] = 4
  numbers[5] = 0
  numbers[6] = 4198512
  numbers[7] = 0
  numbers[8] = 1326609712
  numbers[9] = 32764
malloc an array ...
This malloc'ed array also has five elements (0 to 4)
  array[0] = 0
  array[1] = 1
  array[2] = 2
  array[3] = 3
  array[4] = 4
  array[5] = 0
  array[6] = 133441
  array[7] = 0
  array[8] = 0
  array[9] = 0
Ok

引用数组时,始终要记得追踪数组大小。将数组大小存储在变量中;不要对数组大小进行 硬编码 hard-code 。否则,如果后期该标识符指向另一个不同大小的数组,却忘记更改硬编码的数组长度时,程序就可能会发生数组越界。

3、字符串溢出

字符串只是特定类型的数组。在 C 语言中,字符串是一个由 char 类型值组成的数组,其中用一个零字符表示字符串的结尾。

因此,与数组一样,要注意避免超出字符串的范围。有时也称之为 字符串溢出

使用 gets 函数读取数据是一种很容易发生字符串溢出的行为方式。gets 函数非常危险,因为它不知道在一个字符串中可以存储多少数据,只会机械地从用户那里读取数据。如果用户输入像 foo 这样的短字符串,不会发生意外;但是当用户输入的值超过字符串长度时,后果可能是灾难性的。

下面是一个使用 gets 函数读取城市名称的示例程序。在这个程序中,我还添加了一些未使用的变量,来展示字符串溢出对其他数据的影响:

#include <stdio.h>
#include <string.h>

int
main()
{
  char name[10];                       /* Such as "Chicago" */
  int var1 = 1, var2 = 2;

  /* show initial values */

  printf("var1 = %d; var2 = %d\n", var1, var2);

  /* this is bad .. please don't use gets */

  puts("Where do you live?");
  gets(name);

  /* show ending values */

  printf("<%s> is length %d\n", name, strlen(name));
  printf("var1 = %d; var2 = %d\n", var1, var2);

  /* done */

  puts("Ok");
  return 0;
}

当你测试类似的短城市名称时,该程序运行良好,例如伊利诺伊州的 Chicago 或北卡罗来纳州的Raleigh

var1 = 1; var2 = 2
Where do you live?
Raleigh
<Raleigh> is length 7
var1 = 1; var2 = 2
Ok

威尔士的小镇 Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 有着世界上最长的名字之一。这个字符串有 58 个字符,远远超出了 name 变量中保留的 10 个字符。结果,程序将值存储在内存的其他区域,覆盖了 var1var2 的值:

var1 = 1; var2 = 2
Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
<Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
var1 = 2036821625; var2 = 2003266668
Ok
Segmentation fault (core dumped)

在运行结束之前,程序会用长字符串覆盖内存的其他部分区域。注意,var1var2 的值不再是起始的 12

避免使用 gets 函数,改用更安全的方法来读取用户数据。例如,getline 函数会分配足够的内存来存储用户输入,因此不会因输入长值而发生意外的字符串溢出。

4、重复释放内存

“分配的内存要手动释放”是良好的 C 语言编程原则之一。程序可以使用 malloc 函数为数组和字符串分配内存,该函数会开辟一块内存,并返回一个指向内存中起始地址的指针。之后,程序可以使用 free 函数释放内存,该函数会使用指针将内存标记为未使用。

但是,你应该只使用一次 free 函数。第二次调用 free 会导致意外的后果,可能会毁掉你的程序。下面是一个针对此点的简短示例程序。程序分配了内存,然后立即释放了它。但为了模仿一个健忘但有条理的程序员,我在程序结束时又一次释放了内存,导致两次释放了相同的内存:

#include <stdio.h>
#include <stdlib.h>

int
main()
{
  int *array;

  puts("malloc an array ...");

  array = malloc(sizeof(int) * 5);

  if (array) {
    puts("malloc succeeded");

    puts("Free the array...");
    free(array);
  }

  puts("Free the array...");
  free(array);

  puts("Ok");
}

运行这个程序会导致第二次使用 free 函数时出现戏剧性的失败:

malloc an array ...
malloc succeeded
Free the array...
Free the array...
free(): double free detected in tcache 2
Aborted (core dumped)

要记得避免在数组或字符串上多次调用 free。将 mallocfree 函数定位在同一个函数中,这是避免重复释放内存的一种方法。

例如,一个纸牌游戏程序可能会在主函数中为一副牌分配内存,然后在其他函数中使用这副牌来玩游戏。记得在主函数,而不是其他函数中释放内存。将 mallocfree 语句放在一起有助于避免多次释放内存。

5、使用无效的文件指针

文件是一种便捷的数据存储方式。例如,你可以将程序的配置数据存储在 config.dat 文件中。Bash shell 会从用户家目录中的 .bash_profile 读取初始化脚本。GNU Emacs 编辑器会寻找文件 .emacs 以从中确定起始值。而 Zoom 会议客户端使用 zoomus.conf 文件读取其程序配置。

所以,从文件中读取数据的能力几乎对所有程序都很重要。但是假如要读取的文件不存在,会发生什么呢?

在 C 语言中读取文件,首先要用 fopen 函数打开文件,该函数会返回指向文件的流指针。你可以结合其他函数,使用这个指针来读取数据,例如 fgetc 会逐个字符地读取文件。

如果要读取的文件不存在或程序没有读取权限,fopen 函数会返回 NULL 作为文件指针,这表示文件指针无效。但是这里有一个示例程序,它机械地直接去读取文件,不检查 fopen 是否返回了 NULL

#include <stdio.h>

int
main()
{
  FILE *pfile;
  int ch;

  puts("Open the FILE.TXT file ...");

  pfile = fopen("FILE.TXT", "r");

  /* you should check if the file pointer is valid, but we skipped that */

  puts("Now display the contents of FILE.TXT ...");

  while ((ch = fgetc(pfile)) != EOF) {
    printf("<%c>", ch);
  }

  fclose(pfile);

  /* done */

  puts("Ok");
  return 0;
}

当你运行这个程序时,第一次调用 fgetc 会失败,程序会立即中止:

Open the FILE.TXT file ...
Now display the contents of FILE.TXT ...
Segmentation fault (core dumped)

始终检查文件指针以确保其有效。例如,在调用 fopen 打开一个文件后,用类似 if (pfile != NULL) 的语句检查指针,以确保指针是可以使用的。

人都会犯错,最优秀的程序员也会产生编程错误。但是,遵循上面这些准则,添加一些额外的代码来检查这五种类型的错误,就可以避免最严重的 C 语言编程错误。提前编写几行代码来捕获这些错误,可能会帮你节省数小时的调试时间。


via: https://opensource.com/article/21/10/programming-bugs

作者:Jim Hall 选题:lujun9972 译者:unigeorge 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出