第 2 章. 编程工具

2.1. 摘要

本章介绍了如何使用 FreeBSD 提供的一些编程工具,尽管其中大部分内容也适用于许多其他版本的 UNIX®。它**不**试图详细描述任何编码。本章的大部分内容假设很少或没有以前的编程知识,尽管希望大多数程序员都能从中找到有价值的内容。

2.2. 简介

FreeBSD 提供了一个优秀的开发环境。基本系统附带了 C 和 C++ 编译器以及汇编器,更不用说 sedawk 等经典的 UNIX® 工具了。如果这还不够,Ports 集合中还有许多其他编译器和解释器。下一节,编程入门,列出了一些可用的选项。FreeBSD 与 POSIX® 和 ANSI C 等标准以及自身的 BSD 遗产非常兼容,因此可以编写在各种平台上几乎无需修改即可编译和运行的应用程序。

但是,如果您以前从未在 UNIX® 平台上编写过程序,那么所有这些功能一开始可能会让人不知所措。本文档旨在帮助您快速上手,而不会深入探讨更高级的主题。本文档的目的是为您提供足够的基础知识,以便能够理解文档。

本文档的大部分内容只需要很少或不需要任何编程知识,但它假设您具备基本的 UNIX® 使用能力并愿意学习!

2.3. 编程入门

程序是一组指令,告诉计算机执行各种操作;有时它需要执行的指令取决于它执行先前指令时发生了什么。本节概述了提供这些指令或“命令”(通常称为)的两种主要方式。一种方式使用解释器,另一种方式使用编译器。由于人类语言对于计算机来说难以以明确的方式理解,因此命令通常是用为特定目的而专门设计的某种语言编写的。

2.3.1. 解释器

使用解释器时,语言作为一种环境提供,您可以在提示符下输入命令,环境会为您执行这些命令。对于更复杂的程序,您可以将命令输入到文件中,并让解释器加载文件并执行其中的命令。如果出现任何错误,许多解释器会将您放入调试器以帮助您跟踪问题。

这样做的优点是您可以立即看到命令的结果,并且可以轻松地更正错误。最大的缺点是当您想与他人共享程序时。他们必须拥有相同的解释器,或者您必须有办法将其提供给他们,并且他们需要了解如何使用它。此外,用户可能不希望在按下错误键时被抛入调试器!从性能角度来看,解释器可能会消耗大量内存,并且通常不会像编译器那样有效地生成代码。

在我看来,如果您以前从未进行过任何编程,那么解释型语言是最好的入门方式。这种类型的环境通常与 Lisp、Smalltalk、Perl 和 Basic 等语言一起使用。也可以认为 UNIX® shell(shcsh)本身就是一个解释器,实际上许多人确实编写 shell “脚本”来帮助执行其机器上的各种“维护”任务。事实上,最初的 UNIX® 理念的一部分是提供大量可以链接在一起的小型实用程序,以 shell 脚本的形式执行有用的任务。

2.3.2. FreeBSD 可用的解释器

以下是 FreeBSD Ports 集合中可用的解释器列表,并简要讨论了一些更流行的解释型语言。

有关如何从 Ports 集合获取和安装应用程序的说明,请参阅手册中的Ports 部分

BASIC

是 Beginner’s All-purpose Symbolic Instruction Code 的缩写。BASIC 开发于 20 世纪 50 年代,用于教授大学生编程,并在 20 世纪 80 年代随每台自称合格的个人电脑一起提供,对许多程序员来说是他们的第一门编程语言。它也是 Visual Basic 的基础。

Bywater Basic 解释器可在 Ports 集合中找到,位于 lang/bwbasic,而 Phil Cockroft 的 Basic 解释器(以前称为 Rabbit Basic)可在 lang/pbasic 中找到。

Lisp

一种开发于 20 世纪 50 年代末的语言,作为当时流行的“数值计算”语言的替代方案。Lisp 不是基于数字,而是基于列表;实际上,名称是“列表处理”的缩写。它在 AI(人工智能)领域非常流行。

Lisp 是一种极其强大而复杂的语言,但可能相当庞大且笨拙。

各种可以在 UNIX® 系统上运行的 Lisp 实现都可以在 FreeBSD 的 Ports 集合中找到。Bruno Haible 和 Michael Stoll 编写的 CLISP 位于 lang/clisp。SLisp 是一种更简单的 Lisp 实现,位于 lang/slisp

Perl

系统管理员编写脚本时非常流行;也常用于 Web 服务器编写 CGI 脚本。

Perl 在 Ports 集合中可用,对于所有 FreeBSD 版本,位于 lang/perl5.36

Scheme

Lisp 的一种方言,比 Common Lisp 更紧凑和简洁。在大学中很流行,因为它足够简单,可以作为本科生的第一门语言进行教学,同时它也具有足够高的抽象级别,可用于研究工作。

Scheme 可从 Ports 集合中获得,对于 Elk Scheme 解释器,位于 lang/elk。MIT Scheme 解释器位于 lang/mit-scheme,而 SCM Scheme 解释器位于 lang/scm

Lua

Lua 是一种轻量级的可嵌入脚本语言。它具有广泛的可移植性和相对的简单性。Lua 在 Ports 集合中可用,位于 lang/lua54。它也包含在基本系统中,作为 /usr/libexec/flua 供基本系统组件使用。第三方软件不应该依赖于 flua

Python

Python 是一种面向对象的解释型语言。其拥护者认为它是开始编程的最佳语言之一,因为它相对易于上手,但在与其他用于开发大型复杂应用程序的流行解释型语言(Perl 和 Tcl 是另外两种流行于此类任务的语言)相比,它并不受限。

最新版本的 Python 可从 Ports 集合中获得,位于 lang/python

Ruby

Ruby 是一种解释型、纯面向对象编程语言。由于其易于理解的语法、编写代码的灵活性以及轻松开发和维护大型复杂程序的能力,它已变得非常流行。

Ruby 可在 Ports Collection 中获取,位于 lang/ruby32

Tcl 和 Tk

Tcl 是一种可嵌入的解释型语言,它已得到广泛使用,并且主要因其可移植到许多平台而变得流行。它既可以用于快速编写小型原型应用程序,也可以(当与 GUI 工具包 Tk 结合使用时)用于编写功能齐全的完整程序。

FreeBSD 提供了各种版本的 Tcl 端口。最新版本 Tcl 8.7 可以在 lang/tcl87 中找到。

2.3.3. 编译器

编译器的工作方式有所不同。首先,您使用编辑器将代码写入文件(或多个文件)。然后运行编译器,查看它是否接受您的程序。如果未编译成功,请咬牙切齿地回到编辑器;如果编译成功并生成了程序,则可以在 shell 命令提示符下或调试器中运行它,以查看其是否正常工作。[1]

显然,这不像使用解释器那样直接。但是,它允许您执行许多使用解释器非常困难甚至不可能的事情,例如编写与操作系统紧密交互的代码,甚至编写您自己的操作系统!如果您需要编写非常高效的代码,它也很有用,因为编译器可以花费时间并优化代码,这在解释器中是不可接受的。此外,分发为编译器编写的程序通常比为解释器编写的程序更简单 - 您可以只给他们一个可执行文件的副本,假设他们与您拥有相同的操作系统。

由于在使用单独程序时,编辑-编译-运行-调试循环相当繁琐,因此许多商业编译器制造商已经开发了集成开发环境(简称 IDE)。FreeBSD 在基本系统中不包含 IDE,但 devel/kdevelop 可在 Ports Collection 中使用,许多人为此目的使用 Emacs。在 使用 Emacs 作为开发环境 中讨论了如何使用 Emacs 作为 IDE。

2.4. 使用 cc 编译

本节介绍用于 C 和 C++ 的 clang 编译器,因为它已安装在 FreeBSD 基本系统中。Clang 安装为 cc;GNU 编译器 gcc 可在 Ports Collection 中使用。使用解释器生成程序的细节在不同的解释器之间差异很大,通常在解释器的文档和在线帮助中都有很好的介绍。

完成您的杰作后,下一步是将其转换为可以在 FreeBSD 上(希望!)运行的内容。这通常涉及几个步骤,每个步骤都由一个单独的程序完成。

  1. 预处理源代码以删除注释并执行其他操作,例如扩展 C 中的宏。

  2. 检查代码的语法,查看您是否遵守了语言规则。如果您没有遵守,它会报错!

  3. 将源代码转换为汇编语言 - 这非常接近机器代码,但仍然可以让人理解。据称。

  4. 将汇编语言转换为机器代码 - 是的,我们这里说的是比特和字节,1 和 0。

  5. 检查您是否以一致的方式使用了函数和全局变量等内容。例如,如果您调用了不存在的函数,它会报错。

  6. 如果您尝试从多个源代码文件生成可执行文件,请确定如何将它们全部组合在一起。

  7. 确定如何生成系统运行时加载程序能够加载到内存中并运行的内容。

  8. 最后,将可执行文件写入文件系统。

术语“编译”通常仅指步骤 1 到 4 - 其余部分称为“链接”。有时将步骤 1 称为“预处理”,将步骤 3-4 称为“汇编”。

幸运的是,几乎所有这些细节都对您隐藏了,因为 cc 是一个前端,它负责为您管理调用所有这些程序并传递正确的参数;只需键入

% cc foobar.c

将导致 foobar.c 通过以上所有步骤进行编译。如果您有多个文件要编译,只需执行以下操作:

% cc foo.c bar.c

请注意,语法检查仅仅是检查语法。它不会检查您可能犯的任何逻辑错误,例如将程序置于无限循环中,或者在您打算使用二分查找时使用冒泡排序。[2]

cc 具有许多选项,所有这些选项都在手册页中。以下是一些最重要的选项,以及如何使用它们的示例。

-o filename

文件的输出名称。如果您不使用此选项,cc 将生成一个名为 a.out 的可执行文件。[3]

% cc foobar.c               executable is a.out
% cc -o foobar foobar.c     executable is foobar
-c

仅编译文件,不链接它。对于您只想检查语法的玩具程序或使用 Makefile 时很有用。

% cc -c foobar.c

这将生成一个名为 foobar.o目标文件(而不是可执行文件)。这可以与其他目标文件链接在一起形成可执行文件。

-g

创建可执行文件的调试版本。这会使编译器将有关哪个源文件的哪一行对应于哪个函数调用的信息放入可执行文件中。调试器可以使用此信息在您逐步执行程序时显示源代码,这非常有用;缺点是所有这些额外的信息使程序变得更大。通常,在开发程序时使用 -g 进行编译,然后在您满意它正常工作后,使用不带 -g 的编译“发行版本”。

% cc -g foobar.c

这将生成程序的调试版本。[4]

-O

创建可执行文件的优化版本。编译器执行各种巧妙的技巧,以尝试生成比正常运行速度更快的可执行文件。您可以在 -O 后添加一个数字以指定更高的优化级别,但这通常会导致编译器优化器中的错误。

% cc -O -o foobar foobar.c

这将生成 foobar 的优化版本。

以下三个标志将强制 cc 检查您的代码是否符合相关的国际标准,通常称为 ANSI 标准,尽管严格来说它是 ISO 标准。

-Wall

启用 cc 作者认为有价值的所有警告。尽管名称如此,它不会启用 cc 能够启用的所有警告。

-ansi

关闭 cc 提供的大多数(但不是全部)非 ANSI C 功能。尽管名称如此,它并不能严格保证您的代码将符合标准。

-pedantic

关闭 cc所有非 ANSI C 功能。

如果没有这些标志,cc 将允许您使用其一些对标准的非标准扩展。其中一些非常有用,但与其他编译器不兼容 - 事实上,标准的主要目标之一是允许人们编写可在任何系统上的任何编译器上运行的代码。这称为可移植代码

通常,您应该尝试使您的代码尽可能地可移植,否则您可能以后需要完全重写程序才能使其在其他地方工作 - 谁知道几年后您可能会使用什么?

% cc -Wall -ansi -pedantic -o foobar foobar.c

这将在检查 foobar.c 的标准合规性后生成可执行文件 foobar

-llibrary

指定在链接时要使用的函数库。

最常见的示例是在编译使用 C 中某些数学函数的程序时。与大多数其他平台不同,这些函数位于与标准 C 库不同的库中,您必须告诉编译器添加它。

规则是,如果库名为 libsomething.a,则向 cc 提供参数 -lsomething。例如,数学库是 libm.a,因此您向 cc 提供参数 -lm。数学库的一个常见“陷阱”是它必须是命令行上的最后一个库。

% cc -o foobar foobar.c -lm

这会将数学库函数链接到 foobar 中。

如果您正在编译 C++ 代码,请使用 c++。c++ 也可以在 FreeBSD 上作为 clang++ 调用。

% c++ -o foobar foobar.cc

这将从 C++ 源文件 foobar.cc 生成可执行文件 foobar

2.4.1. 常用 cc 查询和问题

2.4.1.1. 我编译了一个名为 foobar.c 的文件,但我找不到名为 foobar 的可执行文件。它去哪里了?

请记住,除非您另行指定,否则 cc 将调用可执行文件 a.out。使用 -o filename 选项

% cc -o foobar foobar.c

2.4.1.2. 好吧,我有一个名为 foobar 的可执行文件,在运行 ls 时可以看到它,但是当我在命令提示符下键入 foobar 时,它告诉我没有这样的文件。为什么它找不到它?

与 MS-DOS® 不同,UNIX® 在尝试确定要运行哪个可执行文件时不会查看当前目录,除非您告诉它。键入 ./foobar,这意味着“运行当前目录中名为 foobar 的文件”。

2.4.2. 我将我的可执行文件命名为 test,但是当我运行它时什么也没有发生。发生了什么事?

大多数 UNIX® 系统在 /usr/bin 中都有一个名为 test 的程序,并且 shell 会在检查当前目录之前拾取该程序。要么键入

% ./test

要么为您的程序选择一个更好的名称!

2.4.2.1. 我编译了我的程序,它最初似乎运行良好,然后出现错误,并显示有关核心转储的信息。这是什么意思?

术语“核心转储”可以追溯到 UNIX® 的早期,当时机器使用核心内存存储数据。基本上,如果程序在某些条件下失败,系统会将核心内存的内容写入磁盘中的一个名为 core 的文件中,然后程序员可以仔细检查该文件以找出问题所在。

2.4.2.2. 很有趣,但是我现在应该做什么?

使用调试器分析核心(请参阅 调试)。

2.4.2.3. 当我的程序出现核心转储时,它提示了一些关于段错误的信息。这是什么意思?

这基本上意味着你的程序尝试对内存执行某种非法操作;UNIX®被设计用来保护操作系统和其他程序免受恶意程序的侵害。

常见的原因包括:

  • 尝试写入空指针,例如:

    char *foo = NULL;
    strcpy(foo, "bang!");
  • 使用未初始化的指针,例如:

    char *foo;
    strcpy(foo, "bang!");

    指针将包含某个随机值,如果幸运的话,它将指向你的程序无法访问的内存区域,内核将在其造成任何损害之前终止你的程序。如果不幸的话,它将指向你程序内部的某个位置,并破坏你的数据结构之一,导致程序以神秘的方式失败。

  • 尝试访问数组末尾之外的区域,例如:

    int bar[20];
    bar[27] = 6;
  • 尝试将数据存储到只读内存中,例如:

    char *foo = "My string";
    strcpy(foo, "bang!");

    UNIX®编译器通常将字符串字面量(如"My string")放入内存的只读区域。

  • malloc()free()进行不当操作,例如:

    char bar[80];
    free(bar);

    char *foo = malloc(27);
    free(foo);
    free(foo);

犯这些错误并不一定会导致错误,但它们始终是不好的做法。某些系统和编译器比其他系统和编译器更容忍错误,这就是为什么在某个系统上运行良好的程序在另一个系统上运行时可能崩溃的原因。

2.4.2.4. 有时候当我的程序出现核心转储时,它提示总线错误。我的UNIX®书籍上说这表示硬件问题,但计算机似乎仍然可以正常工作。这是真的吗?

不,幸运的是不是(除非你真的遇到了硬件问题……)。这通常是另一种说法,表示你以不应该的方式访问了内存。

2.4.2.5. 这种核心转储机制听起来非常有用,如果我能够在需要时使其发生。我能做到吗?还是必须等到发生错误时才能进行?

可以,只需切换到另一个控制台或xterm,执行以下操作:

% ps

查找程序的进程ID,然后执行:

% kill -ABRT pid

其中pid是你查找到的进程ID。

例如,如果你的程序陷入无限循环,这将非常有用。如果你的程序碰巧捕获了SIGABRT信号,还有其他几个信号具有类似的效果。

或者,你可以在程序内部通过调用abort()函数创建核心转储。查看abort(3)的手册页以了解更多信息。

如果你想从程序外部创建核心转储,但不想终止进程,可以使用gcore程序。查看gcore(1)的手册页以了解更多信息。

2.5. Make

2.5.1. 什么是make

当你处理一个只有1或2个源文件的简单程序时,输入以下命令:

% cc file1.c file2.c

并不是太麻烦,但是当有多个文件时,它很快就会变得非常繁琐——而且编译也需要一段时间。

解决此问题的一种方法是使用目标文件,并且仅在源代码更改时重新编译源文件。因此,如果我们上次编译后只更改了file37.c,而不是其他任何文件,我们可以执行以下操作:

% cc file1.o file2.o … file37.c …

这可能会大大加快编译速度,但并不能解决输入问题。

或者我们可以编写一个shell脚本解决输入问题,但它必须重新编译所有内容,这在大型项目中效率非常低。

如果我们有数百个源文件怎么办?如果我们与其他忘记在更改他们使用的源文件时告知我们的人员在一个团队中工作怎么办?

也许我们可以将这两个解决方案结合起来,并编写类似shell脚本的东西,其中包含某种神奇的规则来指示何时需要编译源文件。现在我们只需要一个能够理解这些规则的程序,因为对于shell来说,这有点太复杂了。

这个程序叫做make。它读取一个名为makefile的文件,该文件告诉它不同的文件如何相互依赖,并计算出哪些文件需要重新编译,哪些文件不需要。例如,一个规则可能表示“如果fromboz.ofromboz.c旧,则意味着有人必须更改了fromboz.c,因此需要重新编译。”makefile还包含告诉make如何重新编译源文件的规则,使其成为一个功能更强大的工具。

Makefile通常保存在与其适用的源文件相同的目录中,并且可以称为makefileMakefileMAKEFILE。大多数程序员使用Makefile这个名称,因为这使得它位于目录列表的顶部附近,便于查看。[5]

2.5.2. 使用make的示例

这是一个非常简单的makefile:

foo: foo.c
	cc -o foo foo.c

它由两行组成:依赖行和创建行。

此处的依赖行由程序名称(称为目标)后跟冒号、空格,然后是源文件名称组成。当make读取此行时,它会查看foo是否存在;如果存在,它会将foo上次修改的时间与foo.c上次修改的时间进行比较。如果foo不存在或比foo.c旧,它会查看创建行以确定要执行的操作。换句话说,这是确定何时需要重新编译foo.c的规则。

创建行以制表符(按tab键)开头,然后是如果你在命令提示符下执行此操作时将键入的创建foo的命令。如果foo已过期或不存在,则make会执行此命令来创建它。换句话说,这是告诉make如何重新编译foo.c的规则。

因此,当你键入make时,它将确保foo与你对foo.c的最新更改保持最新。这个原则可以扩展到包含数百个目标的Makefile——事实上,在FreeBSD上,只需在适当的目录中键入make world就可以编译整个操作系统!

makefile的另一个有用特性是目标不必是程序。例如,我们可以有一个如下所示的makefile:

foo: foo.c
	cc -o foo foo.c

install:
	cp foo /home/me

我们可以通过键入以下命令告诉make我们想要创建哪个目标:

% make target

然后make只会查看该目标并忽略任何其他目标。例如,如果我们使用上面的makefile键入make foo,make将忽略install目标。

如果我们单独键入make,make将始终查看第一个目标,然后停止而不会查看任何其他目标。因此,如果我们在此处键入make,它只会转到foo目标,如有必要重新编译foo,然后停止而不会继续到install目标。

请注意,install目标实际上不依赖于任何内容!这意味着当我们尝试通过键入make install创建该目标时,始终会执行下一行上的命令。在这种情况下,它会将foo复制到用户的home目录。应用程序makefile经常使用此功能,以便在应用程序正确编译后将其安装到正确的目录中。

这是一个稍微有些难以解释的主题。如果你不太理解make的工作原理,最好的方法是编写一个简单的程序,例如“hello world”,以及一个类似于上面示例的makefile,并进行实验。然后逐渐使用多个源文件,或让源文件包含头文件。touch在这里非常有用——它可以更改文件的日期,而无需你进行编辑。

2.5.3. Make和包含文件

C代码通常以要包含的文件列表开头,例如stdio.h。其中一些文件是系统包含文件,另一些文件来自你现在正在处理的项目

#include <stdio.h>
#include "foo.h"

int main(....

为了确保在foo.h更改时立即重新编译此文件,你必须将其添加到你的Makefile

foo: foo.c foo.h

当你的项目越来越大,并且有越来越多的自己的包含文件需要维护时,跟踪所有包含文件以及依赖于它的文件将变得很麻烦。如果你更改了包含文件,但忘记重新编译所有依赖于它的文件,结果将是灾难性的。clang有一个选项可以分析你的文件并生成包含文件及其依赖项的列表:-MM

如果将其添加到你的Makefile中

depend:
	cc -E -MM *.c > .depend

并运行make depend,将出现包含目标文件、C文件和包含文件的列表文件.depend

foo.o: foo.c foo.h

如果你更改了foo.h,下次运行make时,所有依赖于foo.h的文件都将被重新编译。

每次向文件添加包含文件时,请不要忘记运行make depend

2.5.4. FreeBSD Makefile

Makefile编写起来可能相当复杂。幸运的是,像FreeBSD这样的基于BSD的系统将一些非常强大的Makefile作为系统的一部分提供。FreeBSD ports系统就是一个很好的例子。以下是典型ports Makefile的基本部分:

MASTER_SITES=   ftp://freefall.cdrom.com/pub/FreeBSD/LOCAL_PORTS/
DISTFILES=      scheme-microcode+dist-7.3-freebsd.tgz

.include <bsd.port.mk>

现在,如果我们转到此端口的目录并键入make,则会发生以下情况:

  1. 检查系统上是否已存在此端口的源代码。

  2. 如果不存在,则建立到MASTER_SITES中URL的FTP连接以下载源代码。

  3. 计算源代码的校验和,并将其与已知良好源代码副本的校验和进行比较。这是为了确保源代码在传输过程中没有损坏。

  4. 应用使源代码在FreeBSD上工作的任何必要更改——这称为修补

  5. 源代码所需的任何特殊配置都已完成。(许多 UNIX® 程序发行版试图找出它们正在哪个版本的 UNIX® 上编译以及哪些可选的 UNIX® 功能可用——这就是在 FreeBSD ports 场景中提供给它们信息的地方)。

  6. 编译程序的源代码。实际上,我们切换到解压缩源代码的目录并执行 make——程序自己的 makefile 具有构建程序所需的信息。

  7. 现在我们有了程序的编译版本。如果需要,我们现在可以测试它;当我们对程序有信心时,可以键入 make install。这将导致程序及其所需的任何支持文件被复制到正确的位置;还会在 包数据库中创建一个条目,以便以后如果我们改变主意,可以轻松地卸载该端口。

现在我想你会同意,对于一个四行脚本来说,这相当令人印象深刻!

秘密在于最后一行,它告诉 make 在名为 bsd.port.mk 的系统 makefile 中查找。很容易忽略这一行,但所有聪明的东西都来自这里——有人编写了一个 makefile,它告诉 make 执行上述所有操作(以及我未提及的其他一些操作,包括处理可能发生的任何错误),并且任何人都可以通过在自己的 makefile 中添加一行来访问它!

如果想查看这些系统 makefile,它们位于 /usr/share/mk 中,但最好等到您对 makefile 有了一些实践经验后再查看,因为它们非常复杂(如果您确实查看了它们,请确保手边有一瓶浓咖啡!)。

2.5.5. make 的更高级用法

Make 是一款非常强大的工具,它可以完成比上面简单示例中显示的更多操作。不幸的是,make 有几个不同的版本,它们之间存在很大差异。学习它们功能的最佳方法可能是阅读文档——希望本介绍能为您提供一个基础,以便您能够做到这一点。 make(1) 手册页提供了关于变量、参数以及如何使用 make 的全面讨论。

端口中的许多应用程序都使用 GNU make,它有一套非常好的“info”页面。如果您安装了任何这些端口,GNU make 将会自动安装为 gmake。它也可以作为独立的端口和软件包使用。

要查看 GNU make 的 info 页面,您需要编辑 dir(位于 /usr/local/info 目录中)以添加一个条目。这涉及添加如下所示的一行:

 * Make: (make).                 The GNU Make utility.

到文件中。完成后,可以键入 info,然后从菜单中选择 make(或者在 Emacs 中,执行 C-h i)。

2.6. 调试

2.6.1. 可用调试器的简介

使用调试器可以使程序在更受控的环境下运行。通常,可以逐行单步执行程序、检查变量的值、更改它们、告诉调试器运行到某个点然后停止,等等。还可以附加到正在运行的程序,或加载核心文件以调查程序崩溃的原因。

本节旨在快速介绍如何使用调试器,不涵盖内核调试等专业主题。有关这方面的更多信息,请参阅 内核调试

FreeBSD 提供的标准调试器称为 lldb(LLVM 调试器)。由于它是该版本的标准安装的一部分,因此无需执行任何特殊操作即可使用它。它具有良好的命令帮助,可以通过 help 命令访问,以及 网络教程和文档

lldb 命令也可以 从端口或软件包 中获取,例如 devel/llvm

FreeBSD 提供的另一个调试器称为 gdb(GNU 调试器)。与 lldb 不同,它不是 FreeBSD 上的默认安装;要使用它,请 安装 devel/gdb(来自端口或软件包)。它有优秀的在线帮助,以及一套 info 页面。

这两个调试器具有类似的功能集,因此使用哪个调试器主要取决于个人喜好。如果只熟悉其中一个,请使用那个。不熟悉任何一个或都熟悉但希望从 Emacs 内部使用一个的人需要使用 gdb,因为 Emacs 不支持 lldb。否则,尝试两者,看看您更喜欢哪个。

2.6.2. 使用 lldb

2.6.2.1. 启动 lldb

通过键入以下命令启动 lldb:

% lldb -- progname

2.6.2.2. 使用 lldb 运行程序

使用 -g 编译程序以充分利用 lldb。即使不使用它,它也能工作,但只会显示当前正在运行的函数名称,而不是源代码。如果在设置断点时显示类似以下内容的行:

Breakpoint 1: where = temp`main, address =

(没有显示源代码文件名和行号的指示),则表示程序不是使用 -g 编译的。

大多数 lldb 命令都有可以使用的简短形式。为了清楚起见,此处使用较长的形式。

lldb 提示符下,键入 breakpoint set -n main。这将告诉调试器不要显示正在运行的程序中的初步设置代码,并在程序代码的开头停止执行。现在键入 process launch 实际启动程序——它将从设置代码的开头开始,然后在调用 main() 时被调试器停止。

要逐行单步执行程序,请键入 thread step-over。当程序到达函数调用时,通过键入 thread step-in 进入它。进入函数调用后,通过键入 thread step-out 从中返回,或使用 updown 快速查看调用方。

以下是如何使用 lldb 查找程序中错误的一个简单示例。这是我们的程序(包含一个故意的错误):

#include <stdio.h>

int bazz(int anint);

main() {
	int i;

	printf("This is my program\n");
	bazz(i);
	return 0;
}

int bazz(int anint) {
	printf("You gave me %d\n", anint);
	return anint;
}

此程序将 i 设置为 5 并将其传递给函数 bazz(),该函数打印出我们给它的数字。

编译并运行程序将显示:

% cc -g -o temp temp.c
% ./temp
This is my program
anint = -5360

这不是预期的结果!是时候看看发生了什么了!

% lldb -- temp
(lldb) target create "temp"
Current executable set to 'temp' (x86_64).
(lldb) breakpoint set -n main				Skip the set-up code
Breakpoint 1: where = temp`main + 15 at temp.c:8:2, address = 0x00000000002012ef	lldb puts breakpoint at main()
(lldb) process launch					Run as far as main()
Process 9992 launching
Process 9992 launched: '/home/pauamma/tmp/temp' (x86_64)	Program starts running

Process 9992 stopped
* thread #1, name = 'temp', stop reason = breakpoint 1.1	lldb stops at main()
    frame #0: 0x00000000002012ef temp`main at temp.c:8:2
   5	main() {
   6		int i;
   7
-> 8		printf("This is my program\n");			Indicates the line where it stopped
   9		bazz(i);
   10		return 0;
   11	}
(lldb) thread step-over			Go to next line
This is my program						Program prints out
Process 9992 stopped
* thread #1, name = 'temp', stop reason = step over
    frame #0: 0x0000000000201300 temp`main at temp.c:9:7
   6		int i;
   7
   8		printf("This is my program\n");
-> 9		bazz(i);
   10		return 0;
   11	}
   12
(lldb) thread step-in			step into bazz()
Process 9992 stopped
* thread #1, name = 'temp', stop reason = step in
    frame #0: 0x000000000020132b temp`bazz(anint=-5360) at temp.c:14:29	lldb displays stack frame
   11	}
   12
   13	int bazz(int anint) {
-> 14		printf("You gave me %d\n", anint);
   15		return anint;
   16	}
(lldb)

等一下!anint 怎么会变成 -5360?它不是在 main() 中被设置为 5 吗?让我们回到 main() 看一看。

(lldb) up		Move up call stack
frame #1: 0x000000000020130b temp`main at temp.c:9:2		lldb displays stack frame
   6		int i;
   7
   8		printf("This is my program\n");
-> 9		bazz(i);
   10		return 0;
   11	}
   12
(lldb) frame variable i			Show us the value of i
(int) i = -5360							lldb displays -5360

哦,天哪!查看代码后,我们忘记初始化 i 了。我们本意是写:

...
main() {
	int i;

	i = 5;
	printf("This is my program\n");
...

但我们省略了 i=5; 行。由于我们没有初始化 i,因此它包含程序运行时该内存区域中碰巧存在的任何数字,在本例中恰好是 -5360

lldb 命令在每次进入或退出函数时都会显示堆栈帧,即使我们使用 updown 在调用堆栈中移动也是如此。这将显示函数的名称及其参数的值,这有助于我们跟踪当前位置和正在发生的事情。(堆栈是一个存储区域,程序在其中存储有关传递给函数的参数以及从函数调用返回后转到何处的信息。)

2.6.2.3. 使用 lldb 检查核心文件

核心文件基本上是一个文件,其中包含进程崩溃时的完整状态。“在过去的好日子里”,程序员必须打印出核心文件的十六进制列表并仔细研究机器代码手册,但现在生活变得容易了一些。顺便说一句,在 FreeBSD 和其他 4.4BSD 系统下,核心文件被称为 progname.core 而不是仅仅 core,以便更清楚地表明哪个程序属于哪个核心文件。

要检查核心文件,除了程序本身外,还需要指定核心文件的名称。不要以通常的方式启动 lldb,而是键入 lldb -c progname.core -- progname

调试器将显示类似以下内容的信息:

% lldb -c progname.core -- progname
(lldb) target create "progname" --core "progname.core"
Core file '/home/pauamma/tmp/progname.core' (x86_64) was loaded.
(lldb)

在本例中,程序称为 progname,因此核心文件称为 progname.core。调试器不会显示程序崩溃的原因或位置。为此,请使用 thread backtrace all。这还将显示调用程序转储核心所在函数的方式。

(lldb) thread backtrace all
 thread #1, name = 'progname', stop reason = signal SIGSEGV
   frame #0: 0x0000000000201347 progname`bazz(anint=5) at temp2.c:17:10
    frame #1: 0x0000000000201312 progname`main at temp2.c:10:2
    frame #2: 0x000000000020110f progname`_start(ap=<unavailable>, cleanup=<unavailable>) at crt1.c:76:7
(lldb)

SIGSEGV 表示程序尝试访问不属于它的内存位置(运行代码或读取/写入数据),但没有提供任何具体信息。为此,请查看文件 temp2.c 中第 10 行的源代码,位于 bazz() 中。回溯还表明,在本例中,bazz() 是从 main() 调用的。

2.6.2.4. 使用 lldb 附加到正在运行的程序

lldb 最棒的功能之一是它可以附加到正在运行的程序。当然,这需要足够的权限才能做到这一点。一个常见的问题是单步执行一个分叉的程序并希望跟踪子程序,但调试器只会跟踪父程序。

要执行此操作,请启动另一个 lldb,使用 ps 查找子程序的进程 ID,然后执行以下操作:

(lldb) process attach -p pid

lldb 中,然后像往常一样进行调试。

为了使该操作正常工作,调用 fork 以创建子程序的代码需要执行以下操作(由 gdb info 页面提供):

...
if ((pid = fork()) < 0)		/* _Always_ check this */
	error();
else if (pid == 0) {		/* child */
	int PauseMode = 1;

	while (PauseMode)
		sleep(10);	/* Wait until someone attaches to us */
	...
} else {			/* parent */
	...

现在只需要附加到子程序,使用 expr PauseMode = 0 将 PauseMode 设置为 0 并等待 sleep() 调用返回即可。

2.6.3. 使用 LLDB 进行远程调试

从 LLDB 12.0.0 版本开始提供所述功能。使用包含早期 LLDB 版本的 FreeBSD 发行版的用户可能希望使用 端口或软件包 中提供的快照,例如 devel/llvm-devel

从 LLDB 12.0.0 开始,FreeBSD 上支持远程调试。这意味着可以启动 lldb-server 以在某个主机上调试程序,而交互式 lldb 客户端则从另一个主机连接到它。

要启动一个新的进程以远程进行调试,请通过键入以下命令在远程服务器上运行 lldb-server

% lldb-server g host:port -- progname

启动后,进程将立即停止,lldb-server 将等待客户端连接。

在本地启动 lldb 并键入以下命令以连接到远程服务器:

(lldb) gdb-remote host:port

lldb-server 也可以附加到正在运行的进程。要执行此操作,请在远程服务器上键入以下内容:

% lldb-server g host:port --attach pid-or-name

2.6.4. 使用 gdb

2.6.4.1. 启动 gdb

通过键入以下命令启动 gdb:

% gdb progname

尽管许多人更喜欢在 Emacs 中运行它。要执行此操作,请键入以下命令:

 M-x gdb RET progname RET

最后,对于那些发现其基于文本的命令提示符样式令人反感的人,Ports Collection 中有一个图形前端(devel/xxgdb)。

2.6.4.2. 使用 gdb 运行程序

使用 -g 编译程序以充分利用 gdb。即使不使用它,它也能工作,但只会显示当前正在运行的函数名称,而不是源代码。类似以下内容的行:

... (no debugging symbols found) ...

gdb 启动时表示程序不是使用 -g 编译的。

gdb提示符下,输入break main。这将告诉调试器跳过正在运行的程序中的初步设置代码,并在程序代码的开头停止执行。现在输入run以启动程序——它将从设置代码的开头开始,然后在调用main()时被调试器停止。

要逐行单步执行程序,请按n。在函数调用处,通过按s进入它。进入函数调用后,通过按f返回,或使用updown快速查看调用者。

这是一个使用gdb查找程序错误的简单示例。这是我们的程序(带有故意错误)

#include <stdio.h>

int bazz(int anint);

main() {
	int i;

	printf("This is my program\n");
	bazz(i);
	return 0;
}

int bazz(int anint) {
	printf("You gave me %d\n", anint);
	return anint;
}

此程序将 i 设置为 5 并将其传递给函数 bazz(),该函数打印出我们给它的数字。

编译并运行程序将显示:

% cc -g -o temp temp.c
% ./temp
This is my program
anint = 4231

这不是我们期望的!是时候看看发生了什么了!

% gdb temp
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) break main				Skip the set-up code
Breakpoint 1 at 0x160f: file temp.c, line 9.	gdb puts breakpoint at main()
(gdb) run					Run as far as main()
Starting program: /home/james/tmp/temp		Program starts running

Breakpoint 1, main () at temp.c:9		gdb stops at main()
(gdb) n						Go to next line
This is my program				Program prints out
(gdb) s						step into bazz()
bazz (anint=4231) at temp.c:17			gdb displays stack frame
(gdb)

等一下!anint怎么会变成4231?它不是在main()中设置为5吗?让我们回到main()看看。

(gdb) up					Move up call stack
#1  0x1625 in main () at temp.c:11		gdb displays stack frame
(gdb) p i					Show us the value of i
$1 = 4231					gdb displays 4231

哦,天哪!查看代码后,我们忘记初始化 i 了。我们本意是写:

...
main() {
	int i;

	i = 5;
	printf("This is my program\n");
...

但我们省略了i=5;行。由于我们没有初始化i,所以它包含程序运行时该内存区域中碰巧存在的任何数字,在本例中碰巧是4231

每次进入或退出函数时,gdb命令都会显示堆栈帧,即使我们使用updown在调用堆栈中移动也是如此。这显示了函数的名称及其参数的值,这有助于我们跟踪当前位置和正在发生的事情。(堆栈是一个存储区域,程序在其中存储有关传递给函数的参数以及从函数调用返回时要转到哪里等信息。)

2.6.4.3. 使用gdb检查核心文件

核心文件基本上是一个文件,其中包含进程崩溃时的完整状态。“在过去的好日子里”,程序员必须打印出核心文件的十六进制列表并仔细研究机器代码手册,但现在生活变得容易了一些。顺便说一句,在 FreeBSD 和其他 4.4BSD 系统下,核心文件被称为 progname.core 而不是仅仅 core,以便更清楚地表明哪个程序属于哪个核心文件。

要检查核心文件,请以通常的方式启动gdb。不要输入breakrun,而是输入

(gdb) core progname.core

如果核心文件不在当前目录中,请先键入dir /path/to/core/file

调试器应显示如下内容

% gdb progname
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) core progname.core
Core was generated by `progname'.
Program terminated with signal 11, Segmentation fault.
Cannot access memory at address 0x7020796d.
#0  0x164a in bazz (anint=0x5) at temp.c:17
(gdb)

在本例中,程序称为progname,因此核心文件称为progname.core。我们可以看到,程序由于尝试访问其在名为bazz的函数中不可用的内存区域而崩溃。

有时,能够查看函数的调用方式很有用,因为问题可能发生在复杂程序的调用堆栈中很远的地方。bt导致gdb打印出调用堆栈的回溯

(gdb) bt
#0  0x164a in bazz (anint=0x5) at temp.c:17
#1  0xefbfd888 in end ()
#2  0x162c in main () at temp.c:11
(gdb)

当程序崩溃时会调用end()函数;在本例中,bazz()函数是从main()调用的。

2.6.4.4. 使用gdb附加到正在运行的程序

gdb最棒的功能之一是它可以附加到正在运行的程序。当然,这需要足够的权限才能这样做。一个常见的问题是单步执行一个分支的程序,并希望跟踪子程序,但调试器只会跟踪父程序。

为此,启动另一个gdb,使用ps查找子程序的进程ID,然后执行

(gdb) attach pid

gdb中,然后像往常一样进行调试。

为了使该操作正常工作,调用 fork 以创建子程序的代码需要执行以下操作(由 gdb info 页面提供):

...
if ((pid = fork()) < 0)		/* _Always_ check this */
	error();
else if (pid == 0) {		/* child */
	int PauseMode = 1;

	while (PauseMode)
		sleep(10);	/* Wait until someone attaches to us */
	...
} else {			/* parent */
	...

现在,只需要附加到子进程,将PauseMode设置为0,然后等待sleep()调用返回!

2.7. 使用Emacs作为开发环境

2.7.1. Emacs

Emacs是一个高度可定制的编辑器——事实上,它已经被定制到更像一个操作系统而不是一个编辑器的地步!许多开发人员和系统管理员实际上确实几乎所有时间都在Emacs中工作,只有在注销时才会离开它。

这里甚至不可能总结Emacs可以做的一切,但以下是一些开发人员感兴趣的功能

  • 非常强大的编辑器,允许在字符串和正则表达式(模式)上进行搜索和替换、跳转到块表达式的开头/结尾等,等等。

  • 下拉菜单和在线帮助。

  • 与语言相关的语法高亮显示和缩进。

  • 完全可定制。

  • 您可以在Emacs中编译和调试程序。

  • 在编译错误时,您可以跳转到有问题的源代码行。

  • 用于阅读GNU超文本文档(包括Emacs本身的文档)的info程序的友好前端。

  • gdb的友好前端,允许您在单步执行程序时查看源代码。

以及无疑还有许多被忽略的。

可以使用editors/emacs端口在FreeBSD上安装Emacs。

安装完成后,启动它并执行C-h t以阅读Emacs教程——这意味着按住control,按h,松开control,然后按t。(或者,您可以使用鼠标从Help菜单中选择Emacs Tutorial。)

虽然Emacs确实有菜单,但学习键绑定非常值得,因为当您编辑某些内容时,按几个键比尝试找到鼠标然后点击正确的位置要快得多。而且,当您与经验丰富的Emacs用户交谈时,您会发现他们经常随意地抛出诸如“M-x replace-s RET foo RET bar RET”之类的表达式,因此了解它们的意思很有用。无论如何,Emacs 的有用功能太多,无法全部放在菜单栏上。

幸运的是,学习键绑定非常容易,因为它们显示在菜单项旁边。我的建议是使用菜单项(例如,打开文件)直到您理解它的工作原理并对其充满信心,然后尝试执行C-x C-f。当您对此感到满意时,再继续执行另一个菜单命令。

如果您不记得某个特定的键组合的作用,请从Help菜单中选择Describe Key并输入它——Emacs将告诉您它的作用。您还可以使用Command Apropos菜单项查找所有包含特定单词的命令,以及它旁边的键绑定。

顺便说一下,上面的表达式意味着按住Meta键,按x,释放Meta键,输入replace-sreplace-string的缩写——Emacs的另一个特性是您可以缩写命令),按return键,输入foo(您要替换的字符串),按return键,输入bar(您要替换foo的字符串)并再次按return。然后,Emacs将执行您刚刚请求的搜索和替换操作。

如果您想知道Meta到底是什么,它是一个许多UNIX®工作站拥有的特殊键。不幸的是,PC没有一个,所以它通常是alt(或者如果您不走运,是escape键)。

哦,要退出Emacs,请执行C-x C-c(这意味着按住control键,按x,按c并释放control键)。如果您有任何未保存的文件打开,Emacs将询问您是否要保存它们。(忽略文档中说C-z是离开Emacs的常用方法的部分——这会使Emacs在后台挂起,并且只有在您使用没有虚拟终端的系统时才有用)。

2.7.2. 配置Emacs

Emacs 做了很多很棒的事情;其中一些是内置的,一些需要配置。

Emacs 没有使用专有的宏语言进行配置,而是使用专门为编辑器调整的Lisp版本,称为Emacs Lisp。如果您想继续学习Common Lisp,那么使用Emacs Lisp可能会非常有帮助。Emacs Lisp具有Common Lisp的许多特性,尽管它要小得多(因此更容易掌握)。

学习Emacs Lisp 的最佳方法是阅读在线的Emacs参考手册。

但是,实际上无需了解任何Lisp即可开始配置Emacs,因为我已经包含了一个示例.emacs,这应该足以让您入门。只需将其复制到您的主目录中,如果Emacs已在运行,请重新启动它;它将读取文件中的命令并(希望)为您提供有用的基本设置。

2.7.3. 示例.emacs

不幸的是,这里的内容太多,无法详细解释;但是,有一些要点值得一提。

  • ;开头的所有内容都是注释,Emacs会忽略它。

  • 在第一行中,-- Emacs-Lisp --是为了让我们能够在Emacs中编辑.emacs本身并获得所有用于编辑Emacs Lisp的花哨功能。Emacs通常会根据文件名猜测这一点,并且可能无法正确识别.emacs

  • tab键在某些模式下绑定到缩进函数,因此当您按tab键时,它将缩进当前代码行。如果您想在您正在编写的任何内容中放置一个制表符字符,请在按tab键的同时按住control键。

  • 此文件通过根据文件名猜测语言来支持C、C++、Perl、Lisp和Scheme的语法高亮显示。

  • Emacs已经有一个预定义的函数称为next-error。在编译输出窗口中,这允许您通过执行M-n从一个编译错误移动到下一个编译错误;我们定义了一个补充函数previous-error,它允许您通过执行M-p转到先前的错误。最棒的功能是C-c C-c将打开发生错误的源文件并跳转到相应的行。

  • 我们启用了Emacs充当服务器的功能,因此,如果您在Emacs外部执行某些操作并希望编辑文件,则只需输入

    % emacsclient filename

    然后您就可以在您的Emacs中编辑该文件了![6]

示例1. 示例.emacs
;; -*-Emacs-Lisp-*-

;; This file is designed to be re-evaled; use the variable first-time
;; to avoid any problems with this.
(defvar first-time t
  "Flag signifying this is the first time that .emacs has been evaled")

;; Meta
(global-set-key "\M- " 'set-mark-command)
(global-set-key "\M-\C-h" 'backward-kill-word)
(global-set-key "\M-\C-r" 'query-replace)
(global-set-key "\M-r" 'replace-string)
(global-set-key "\M-g" 'goto-line)
(global-set-key "\M-h" 'help-command)

;; Function keys
(global-set-key [f1] 'manual-entry)
(global-set-key [f2] 'info)
(global-set-key [f3] 'repeat-complex-command)
(global-set-key [f4] 'advertised-undo)
(global-set-key [f5] 'eval-current-buffer)
(global-set-key [f6] 'buffer-menu)
(global-set-key [f7] 'other-window)
(global-set-key [f8] 'find-file)
(global-set-key [f9] 'save-buffer)
(global-set-key [f10] 'next-error)
(global-set-key [f11] 'compile)
(global-set-key [f12] 'grep)
(global-set-key [C-f1] 'compile)
(global-set-key [C-f2] 'grep)
(global-set-key [C-f3] 'next-error)
(global-set-key [C-f4] 'previous-error)
(global-set-key [C-f5] 'display-faces)
(global-set-key [C-f8] 'dired)
(global-set-key [C-f10] 'kill-compilation)

;; Keypad bindings
(global-set-key [up] "\C-p")
(global-set-key [down] "\C-n")
(global-set-key [left] "\C-b")
(global-set-key [right] "\C-f")
(global-set-key [home] "\C-a")
(global-set-key [end] "\C-e")
(global-set-key [prior] "\M-v")
(global-set-key [next] "\C-v")
(global-set-key [C-up] "\M-\C-b")
(global-set-key [C-down] "\M-\C-f")
(global-set-key [C-left] "\M-b")
(global-set-key [C-right] "\M-f")
(global-set-key [C-home] "\M-<")
(global-set-key [C-end] "\M->")
(global-set-key [C-prior] "\M-<")
(global-set-key [C-next] "\M->")

;; Mouse
(global-set-key [mouse-3] 'imenu)

;; Misc
(global-set-key [C-tab] "\C-q\t")	; Control tab quotes a tab.
(setq backup-by-copying-when-mismatch t)

;; Treat 'y' or <CR> as yes, 'n' as no.
(fset 'yes-or-no-p 'y-or-n-p)
(define-key query-replace-map [return] 'act)
(define-key query-replace-map [?\C-m] 'act)

;; Load packages
(require 'desktop)
(require 'tar-mode)

;; Pretty diff mode
(autoload 'ediff-buffers "ediff" "Intelligent Emacs interface to diff" t)
(autoload 'ediff-files "ediff" "Intelligent Emacs interface to diff" t)
(autoload 'ediff-files-remote "ediff"
  "Intelligent Emacs interface to diff")

(if first-time
    (setq auto-mode-alist
	  (append '(("\\.cpp$" . c++-mode)
		    ("\\.hpp$" . c++-mode)
		    ("\\.lsp$" . lisp-mode)
		    ("\\.scm$" . scheme-mode)
		    ("\\.pl$" . perl-mode)
		    ) auto-mode-alist)))

;; Auto font lock mode
(defvar font-lock-auto-mode-list
  (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'lisp-mode 'perl-mode 'scheme-mode)
  "List of modes to always start in font-lock-mode")

(defvar font-lock-mode-keyword-alist
  '((c++-c-mode . c-font-lock-keywords)
    (perl-mode . perl-font-lock-keywords))
  "Associations between modes and keywords")

(defun font-lock-auto-mode-select ()
  "Automatically select font-lock-mode if the current major mode is in font-lock-auto-mode-list"
  (if (memq major-mode font-lock-auto-mode-list)
      (progn
	(font-lock-mode t))
    )
  )

(global-set-key [M-f1] 'font-lock-fontify-buffer)

;; New dabbrev stuff
;(require 'new-dabbrev)
(setq dabbrev-always-check-other-buffers t)
(setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
(add-hook 'emacs-lisp-mode-hook
	  '(lambda ()
	     (set (make-local-variable 'dabbrev-case-fold-search) nil)
	     (set (make-local-variable 'dabbrev-case-replace) nil)))
(add-hook 'c-mode-hook
	  '(lambda ()
	     (set (make-local-variable 'dabbrev-case-fold-search) nil)
	     (set (make-local-variable 'dabbrev-case-replace) nil)))
(add-hook 'text-mode-hook
	  '(lambda ()
	     (set (make-local-variable 'dabbrev-case-fold-search) t)
	     (set (make-local-variable 'dabbrev-case-replace) t)))

;; C++ and C mode...
(defun my-c++-mode-hook ()
  (setq tab-width 4)
  (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (define-key c++-mode-map "\C-ce" 'c-comment-edit)
  (setq c++-auto-hungry-initial-state 'none)
  (setq c++-delete-function 'backward-delete-char)
  (setq c++-tab-always-indent t)
  (setq c-indent-level 4)
  (setq c-continued-statement-offset 4)
  (setq c++-empty-arglist-indent 4))

(defun my-c-mode-hook ()
  (setq tab-width 4)
  (define-key c-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (define-key c-mode-map "\C-ce" 'c-comment-edit)
  (setq c-auto-hungry-initial-state 'none)
  (setq c-delete-function 'backward-delete-char)
  (setq c-tab-always-indent t)
;; BSD-ish indentation style
  (setq c-indent-level 4)
  (setq c-continued-statement-offset 4)
  (setq c-brace-offset -4)
  (setq c-argdecl-indent 0)
  (setq c-label-offset -4))

;; Perl mode
(defun my-perl-mode-hook ()
  (setq tab-width 4)
  (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (setq perl-indent-level 4)
  (setq perl-continued-statement-offset 4))

;; Scheme mode...
(defun my-scheme-mode-hook ()
  (define-key scheme-mode-map "\C-m" 'reindent-then-newline-and-indent))

;; Emacs-Lisp mode...
(defun my-lisp-mode-hook ()
  (define-key lisp-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (define-key lisp-mode-map "\C-i" 'lisp-indent-line)
  (define-key lisp-mode-map "\C-j" 'eval-print-last-sexp))

;; Add all of the hooks...
(add-hook 'c++-mode-hook 'my-c++-mode-hook)
(add-hook 'c-mode-hook 'my-c-mode-hook)
(add-hook 'scheme-mode-hook 'my-scheme-mode-hook)
(add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook)
(add-hook 'lisp-mode-hook 'my-lisp-mode-hook)
(add-hook 'perl-mode-hook 'my-perl-mode-hook)

;; Complement to next-error
(defun previous-error (n)
  "Visit previous compilation error message and corresponding source code."
  (interactive "p")
  (next-error (- n)))

;; Misc...
(transient-mark-mode 1)
(setq mark-even-if-inactive t)
(setq visible-bell nil)
(setq next-line-add-newlines nil)
(setq compile-command "make")
(setq suggest-key-bindings nil)
(put 'eval-expression 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'set-goal-column 'disabled nil)
(if (>= emacs-major-version 21)
	(setq show-trailing-whitespace t))

;; Elisp archive searching
(autoload 'format-lisp-code-directory "lispdir" nil t)
(autoload 'lisp-dir-apropos "lispdir" nil t)
(autoload 'lisp-dir-retrieve "lispdir" nil t)
(autoload 'lisp-dir-verify "lispdir" nil t)

;; Font lock mode
(defun my-make-face (face color &optional bold)
  "Create a face from a color and optionally make it bold"
  (make-face face)
  (copy-face 'default face)
  (set-face-foreground face color)
  (if bold (make-face-bold face))
  )

(if (eq window-system 'x)
    (progn
      (my-make-face 'blue "blue")
      (my-make-face 'red "red")
      (my-make-face 'green "dark green")
      (setq font-lock-comment-face 'blue)
      (setq font-lock-string-face 'bold)
      (setq font-lock-type-face 'bold)
      (setq font-lock-keyword-face 'bold)
      (setq font-lock-function-name-face 'red)
      (setq font-lock-doc-string-face 'green)
      (add-hook 'find-file-hooks 'font-lock-auto-mode-select)

      (setq baud-rate 1000000)
      (global-set-key "\C-cmm" 'menu-bar-mode)
      (global-set-key "\C-cms" 'scroll-bar-mode)
      (global-set-key [backspace] 'backward-delete-char)
					;      (global-set-key [delete] 'delete-char)
      (standard-display-european t)
      (load-library "iso-transl")))

;; X11 or PC using direct screen writes
(if window-system
    (progn
      ;;      (global-set-key [M-f1] 'hilit-repaint-command)
      ;;      (global-set-key [M-f2] [?\C-u M-f1])
      (setq hilit-mode-enable-list
	    '(not text-mode c-mode c++-mode emacs-lisp-mode lisp-mode
		  scheme-mode)
	    hilit-auto-highlight nil
	    hilit-auto-rehighlight 'visible
	    hilit-inhibit-hooks nil
	    hilit-inhibit-rebinding t)
      (require 'hilit19)
      (require 'paren))
  (setq baud-rate 2400)			; For slow serial connections
  )

;; TTY type terminal
(if (and (not window-system)
	 (not (equal system-type 'ms-dos)))
    (progn
      (if first-time
	  (progn
	    (keyboard-translate ?\C-h ?\C-?)
	    (keyboard-translate ?\C-? ?\C-h)))))

;; Under UNIX
(if (not (equal system-type 'ms-dos))
    (progn
      (if first-time
	  (server-start))))

;; Add any face changes here
(add-hook 'term-setup-hook 'my-term-setup-hook)
(defun my-term-setup-hook ()
  (if (eq window-system 'pc)
      (progn
;;	(set-face-background 'default "red")
	)))

;; Restore the "desktop" - do this as late as possible
(if first-time
    (progn
      (desktop-load-default)
      (desktop-read)))

;; Indicate that this file has been read at least once
(setq first-time nil)

;; No need to debug anything now

(setq debug-on-error nil)

;; All done
(message "All done, %s%s" (user-login-name) ".")

2.7.4. 扩展Emacs理解的语言范围

现在,如果您只想使用.emacs中已提供的语言(C、C++、Perl、Lisp和Scheme)进行编程,这很好,但是如果出现了一种名为“whizbang”的新语言,并且充满了令人兴奋的功能,会发生什么情况呢?

首先要弄清楚whizbang是否附带任何告诉Emacs有关该语言的文件。这些文件通常以.el结尾,它是“Emacs Lisp”的缩写。例如,如果whizbang是FreeBSD端口,我们可以通过执行以下操作找到这些文件

% find /usr/ports/lang/whizbang -name "*.el" -print

并将它们安装到Emacs站点Lisp目录中。在FreeBSD上,它是/usr/local/share/emacs/site-lisp

例如,如果find命令的输出是

/usr/ports/lang/whizbang/work/misc/whizbang.el

我们将执行

# cp /usr/ports/lang/whizbang/work/misc/whizbang.el /usr/local/share/emacs/site-lisp

接下来,我们需要决定 whizbang 源文件的扩展名是什么。假设它们都以.wiz结尾。我们需要在我们的.emacs中添加一个条目,以确保 Emacs 能够使用whizbang.el中的信息。

.emacs中找到 auto-mode-alist 条目,并为 whizbang 添加一行,例如

...
("\\.lsp$" . lisp-mode)
("\\.wiz$" . whizbang-mode)
("\\.scm$" . scheme-mode)
...

这意味着当您编辑以.wiz结尾的文件时,Emacs 将自动进入whizbang-mode

就在这下面,您会找到 font-lock-auto-mode-list 条目。像这样向其中添加whizbang-mode

;; Auto font lock mode
(defvar font-lock-auto-mode-list
  (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'whizbang-mode 'lisp-mode 'perl-mode 'scheme-mode)
  "List of modes to always start in font-lock-mode")

这意味着在编辑.wiz文件时,Emacs 将始终启用font-lock-mode(即语法高亮)。

这就是所有需要做的。如果您希望在打开.wiz时自动执行其他操作,您可以添加一个whizbang-mode hook(参见my-scheme-mode-hook,它提供了一个添加auto-indent的简单示例)。

2.8. 进一步阅读

有关设置用于为 FreeBSD 本身贡献修复的开发环境的信息,请参阅development(7)

  • Brian Harvey 和 Matthew Wright Simply Scheme MIT 1994. ISBN 0-262-08226-8

  • Randall Schwartz Learning Perl O’Reilly 1993 ISBN 1-56592-042-2

  • Patrick Henry Winston 和 Berthold Klaus Paul Horn Lisp (第3版) Addison-Wesley 1989 ISBN 0-201-08319-1

  • Brian W. Kernighan 和 Rob Pike The Unix Programming Environment Prentice-Hall 1984 ISBN 0-13-937681-X

  • Brian W. Kernighan 和 Dennis M. Ritchie The C Programming Language (第2版) Prentice-Hall 1988 ISBN 0-13-110362-8

  • Bjarne Stroustrup The C++ Programming Language Addison-Wesley 1991 ISBN 0-201-53992-6

  • W. Richard Stevens Advanced Programming in the Unix Environment Addison-Wesley 1992 ISBN 0-201-56317-7

  • W. Richard Stevens Unix Network Programming Prentice-Hall 1990 ISBN 0-13-949876-1


1。如果您在 shell 中运行它,可能会出现核心转储。
2。如果您不知道,二分查找是一种有效排序方法,而冒泡排序则不是。
3。这样做的原因已经湮没在历史的迷雾之中。
4。请注意,我们没有使用 -o 标记来指定可执行文件名,因此我们将得到一个名为 a.out 的可执行文件。生成名为 foobar 的调试版本留作读者的练习!
5。它们不使用 MAKEFILE 形式,因为大写字母通常用于文档文件,例如 README。
6。许多 Emacs 用户将其 EDITOR 环境设置为 emacsclient,因此每次需要编辑文件时都会发生这种情况。

上次修改时间:2024年9月20日,作者 Fernando Apesteguía