教程:程序员的 Emacs

作者:Matt Welsh

上个月关注过我们的人会记得“Emacs:朋友还是敌人?”,这是一篇为那些除了 vi 之外什么都无法忍受的人准备的教程。“好吧,”你们在问自己,“这个持有 vi 原教旨主义信条的人怎么又在写一篇关于 Emacs 的文章了?” 听起来很可疑,不是吗?

事实是,一旦你掌握了窍门,Emacs 可以极大地简化编辑,尤其是编辑程序源代码。我现在经常使用 Emacs 来开发和调试程序。

它极大地减少了可怕的 编辑-编译-诅咒-调试-编辑 循环中的周转时间。下面是如何以这种方式使用 Emacs。

本教程的大部分内容假设您熟悉 Emacs,以及自定义您的 Emacs 环境(如上个月的教程,在Linux Journal 第 1 卷,第 5 期中讨论的那样)。只要您知道如何将代码添加到您的 .emacs 启动文件(或者,按照上个月的讨论,~/emacs/startup.el),您就可以开始了。

这里描述的功能和注释适用于 GNU Emacs 19.24.1。当您阅读这篇文章时,可能会有更新的版本可用,在这种情况下,您的结果可能会有所不同。

编辑 C 代码

如您所知,Emacs 有几种与编程相关的主要模式。例如,C 模式用于编辑 C 源代码,Perl 模式用于 Perl,等等。首先,我将讨论 C 模式的特性,然后解释如何在 Emacs 中编译和调试 C 程序。

命令 M-x c-mode 用于进入 C 模式。(回忆一下上个月:M-xMeta-x,其中 meta 通常是 <esc> 键。)但是,Emacs 通常能够确定 C 模式应该用于 C 源代码文件,要么通过文件名扩展名 .c,要么如果魔术字符串

-*- C -*-

出现在文件的第一行。如果您对这是如何工作的感兴趣,请参阅 Emacs 关于主要模式的文档。

在 C 模式下,代码会根据几个变量的值自动缩进。这些变量包括 c-indent-levelc-continued- statement-offset 等等。Emacs Info 页面详细描述了这些变量;但是,我发现默认值对于多种缩进样式都非常有效。除非您在代码缩进方面有特别独特的艺术天赋,否则我怀疑您不必摆弄 Emacs 的缩进变量。

当您在行的任何位置按下 TAB 键时,该行将适当地缩进。这不会导致插入制表符;它只是根据上面提到的变量值缩进该行。如果您想实际插入制表符,请在其前面加上 C-q

要了解这是如何工作的,请启动 Emacs 并编辑一个名为 foo.c 的文件。输入几行虚假的 C 代码,每次输入后按 RET 键。然后返回并在每行上按 TAB 键以查看结果。

按 LFD 而不是 RET 等同于按 RET,然后按 TAB—也就是说,您启动一个新行,它会自动为您缩进。我发现将 RET 绑定到此功能特别有用,该功能是 newline-and-indent,这样我在编写代码时不必使用 LFD。在我的 Emacs 配置文件中,我包含以下行

(define-key c-mode-map "\C-m' -newline-and-indent)

您可能已经注意到的一个功能是,右花括号和右括号会自动 闪烁 其对应的左花括号和左括号。这可以帮助您检查代码中的括号是否平衡。如果您不喜欢此功能,可以使用以下方法将其关闭

(setq blink-matching-paren nil)

在您的 .emacs 文件(或 ~/emacs/startup.el,对于那些使用上个月描述的方法的人。此后,我们将仅提及 .emacs,但请记住,所有这些自定义都可以与这两种方法一起使用)。

如果您在 X 下运行 Emacs,paren 库将导致匹配的括号和花括号在光标位于左花括号/括号上或在右花括号/括号之后时突出显示。只需包含

(load-library "paren")

在您的 .emacs 文件中将启用此功能。平衡的括号在 region face 中突出显示;您可以使用诸如 set-face-foregroundset-face-font 等命令更改颜色或字体。

例如,

(set-face-background -0region "pink")

会将此 face 的背景颜色设置为粉红色。当启用 transient-mark-mode 时,region face 也用于显示当前区域。我们将在下面详细讨论 face。

您还会注意到,键入右花括号(在单独的一行上)将取消缩进包含该花括号的行。当

键入如下代码时

int foo() {
  /* Your code here */

在按 RET 键后,下一行将相对于上面的注释缩进(当然,假设您已将 RET 绑定到 newline-and-indent)。现在,在键入右花括号后,您将得到

int foo() {
  /* Your code here */
}

花括号绑定到函数 electric-c-brace,它插入花括号并更正当前行的缩进。花括号及其包含的文本的缩进由 Emacs 变量 c-brace-offsetc-imaginary-brace-offset 等控制。

一般来说,您的代码应遵循 Emacs 设置的缩进样式。添加注释是一个例外。许多程序员喜欢将注释设置在显示器的右边距附近,如下所示

int floof(struct shoop *s, int i) {
   s->fnum = i;     /* You are not expected
                      to understand this */
   return 0;
}

既然 TAB 失去了添加空格的自然能力,我们如何添加这样的注释呢?Emacs 提供了 M-; 命令,它从变量 comment-column 指定的列开始注释,默认设置为 24。当然,您始终可以通过键入以下内容来添加注释

/* ... */ by hand.

您可以使用 M-x comment-region 注释掉当前区域中的所有行。(对于 Emacs 新手,区域是通过将光标移动到特定位置,使用 C-Space 设置“标记”,然后将光标移动到其他位置来定义的。区域是光标和标记之间的文本块。还有各种其他方法可以设置区域;例如,在 X 下,在文本的一部分上拖动鼠标 1 将定义区域。)同样,M-C-\(即 meta-control-backslash)将缩进当前区域。

C 模式还定义了几个新的移动命令。M-C-a 会将光标移动到当前函数的开头。类似地,M-C-e 会将光标移动到当前函数的结尾。但是请注意,“当前函数”由文本第一列中的左花括号或右花括号表示。如果您使用 C 缩进样式,例如

int foo() {
  /* Your code here */
}

Emacs 将无法找到函数的开头,因为左花括号位于行的末尾。为了使这些命令正常工作,您应该像这样缩进您的代码

int foo()
{
  /* Your code here */
}

要将区域选择为当前函数的文本,您可以使用 M-C-h。这提供了一种操作整个函数的便捷方法。例如,快捷键序列 M-C-hC-wM-C-eC-y 将把当前函数移动到以下函数之下。给你的朋友留下深刻印象!

M-aM-e 可用于移动到当前 C 语句(块,以分号分隔的表达式等)的开头或结尾。

最后一个重要的 C 模式特性:宏展开。如果您运行 M-x c-macro-expand,Emacs 将在当前区域上运行 C 预处理器,并在另一个缓冲区中显示结果。例如,给定以下代码

static XtResource resource_list[] = {
  { RES_N_doputimage, RES_C_doputimage,
    XtRBoolean, sizeof(Boolean),
    XtOffset(app_data_ptr,do_putimage),
    XtRImmediate, (XtPointer) FALSE,
  },
};

将此文本选择为区域,并调用 c-macro-expand,我们得到

static XtResource resource_list[] = {
  { "doPutImage", "DoPutImage",
    ((char*)&XtStrings[1561]), sizeof(Boolean),
    ((Cardinal) (((char *)
    (&(((app_data_ptr)0)->do_putimage))) -
    ((char *) 0))),
    ((char*)&XtStrings[1695]), (XtPointer) 0,
  },
};

如果您试图调试复杂的宏,或者需要知道给定预处理器符号的定义,这会很有用。

存在许多其他特定语言的模式,例如 Perl 模式、Emacs LISP 模式、Prolog 模式等等。这些模式中的大多数都共享上述基本功能。了解新模式的最佳方法是进入它(使用诸如 M-x perl-mode 之类的命令),并使用 M-x describe-mode 获取有关其功能的概要。

使用 face

Emacs-19 提供了对 face 的支持,face 允许以各种字体和/或颜色显示不同类型的文本。在上个月的教程中,我们描述了如何在 Emacs 下配置 face;由于 face 的使用对于编辑源代码特别有帮助,因此在此处重复一遍。

命令 M-x list-faces-display 将在另一个窗口中显示当前的 face 及其关联的名称。Face 被赋予诸如 bold、bold-italic 等名称。这些名称不一定与 face 的外观有关——例如,您的 bold face 不必是粗体字体。

函数 set-face-foregroundset-face-background 可用于分别设置 face 的前景色和背景色。set-face-font 设置用于特定 face 的字体;set-face-underline-p 指定是否使用下划线显示特定 face。

Face 最常用于 Font Lock 模式中,Font Lock 模式是一种次要模式,它使当前缓冲区“字体化”——也就是说,文本以不同的 face 显示,具体取决于上下文。例如,当将 Font Lock 模式与 C 模式一起使用时,函数名称以一个 face 显示,注释以另一个 face 显示,预处理器指令以另一个 face 显示,依此类推。这在编辑源代码时是一种令人愉悦的视觉效果;您可以通过浏览显示轻松识别函数名称和注释。

以下函数可以在您的 Emacs 启动文件中用于启用 Font Lock 模式并设置各种 face 的颜色。

defun my-turn-on-font-lock ()
  (interactive "")
  ;;; Color the faces appropriately
  (set-face-foreground -bold "lightblue")
  (set-face-foreground -bold-italic "olivedrab2")
  (set-face-foreground -italic "lightsteelblue")
  (set-face-foreground -modeline "white")
  (set-face-background -modeline "black")
  (set-face-background -highlight "blue")
  ;; Turn off underline property for bold and underline
  (set-face-underline-p -bold nil)
  (set-face-underline-p -underline nil)
  (transient-mark-mode 1)
  (font-lock-mode 1))

请注意,除了启用 font-lock-mode 之外,我还启用了 transient-mark-mode。在此模式下,当前区域使用 region face 进行阴影处理。这可以为您节省大量时间,让您记住当前标记设置的位置。

上面的函数由

(defun my-window-setup-hook ()
   (set-foreground-color "white")
   (set-background-color "dimgray")
   (set-mouse-color "orchid")
   (set-cursor-color "red")
   (my-turn-on-font-lock))
(add-hook 'window-setup-hook 'my-window-setup-hook)

调用。也就是说,Emacs window-setup-hook(在启动时执行)调用 my-window-setup-hook,它首先设置窗口的前景色和背景色,然后启用 Font Lock 模式。

您必须为您希望在其中使用的每个缓冲区单独启用 Font Lock 模式。因此,每当我进入 C 模式、Emacs LISP 模式或 Perl 模式时,我都让 Emacs 调用 my-turn-on-font-lock

(add-hook 'c-mode-hook 'my-turn-on-font-lock)
(add-hook 'emacs-lisp-mode-hook 'my-turn-on-font-lock)
(add-hook 'perl-mode-hook 'my-turn-on-font-lock)

确定如何根据自己的喜好配置 face 的最佳方法是使用上面给出的代码进行实验。有几个变量控制 Font Lock 模式用于特定类型代码的 face。例如,font-lock-comment-face 是用于注释的 face。默认情况下,它的值是 italic,我们在上面将其设置为使用 lightsteelblue 的前景色。您可以直接设置 bold、italic 等 face 属性,也可以操作 font-lock-comment-facefont-lock-function-name-face 等等。使用 M-x apropos 并输入 font-lock 将为您提供与 Font Lock 模式关联的函数和变量的列表。

使用标签

Emacs 有许多处理标签的功能,标签只是源代码中标记的位置。标签最常见的用途是标记函数定义的开头。然后,您可以直接跳转到该函数定义,无论它位于哪个源文件中。

为了处理标签,Emacs 使用标签文件,默认情况下,标签文件在您的源文件所在的目录中命名为 TAGS。在试验标签之前,让我们创建一个标签文件。从 shell 提示符下,使用命令

etags filenames...

其中 filenames 是当前目录中源文件的名称。例如,

etags *.c *.h

这将基于当前目录中的 C 源代码和头文件创建文件 TAGS。

假设我们有三个源文件:grover.c、oscar.c 和 telly.c。这些文件可能包含如下代码

/* grover.c ********************/
int grover() {
  /* Code for grover... */
}
/* oscar.c *********************/
int oscar() {
  /* Code for oscar... */
}
/* telly.c *********************/
int telly_monster() {
  /* Code for telly_monster... */
}
int main(int argc, char *argv[]) {
  /* Code for main... */
}

在这些三个源文件上运行 etags 将为这三个文件中的每个函数创建标签。(将 etags-t 选项一起使用还将包括在源代码中找到的任何 typedef。)

现在,我们可以使用诸如 M-.(即 meta-dot)之类的命令,它将查找给定的标签。当我们在编辑这些源文件之一时按下 M-. 时,Emacs 会询问我们

Find tag: (default oscar)

您可以输入标签(函数名称)的名称,例如 telly_monster,包含该标签的源文件将自动打开,并将光标设置到包含该标签的行。这是一种在编辑时在源文件之间移动的非常快捷的方法。

M-. 的默认标签是根据光标当前所在的单词设置的。因此,如果光标当前位于对函数 oscar() 的调用之上,则按 M-.,然后按 RET 将直接将我们带到 oscar() 的定义。

M-x find-tag-regexp 将查找与给定正则表达式匹配的标签。因此,使用 find-tag-regexp 并给出函数名称的一部分会将您带到该函数(假设您指定的正则表达式对于该函数是唯一的)。如果您有一组类似命名的函数,则使用 M-0 M-.(即 meta-zero meta-dot)将把您带到上次使用 find-tag-regexp 匹配的下一个标签。

类似地,您可以使用 M-x tags-search,它将在当前 TAGS 文件中命名的任何文件中搜索命名的正则表达式。也就是说,tags-search 不会限制其对标签的搜索 - 它将在 TAGS 中列出的文件中搜索任何文本。您可以使用 M-, 搜索给定正则表达式的下一个实例。

另一个有用的功能是标签完成。按 M-TAB 将尝试根据当前标签文件中列出的函数完成当前单词。因此,在调用函数 telly_monster 时,我们可以键入 tel M-TAB,它将为我们完成名称。如果给定的单词有多个完成项,则会打开一个 *Completions* 缓冲区,列出所有可能的选择。在 X 下,在完成项上按鼠标 2 将选择它。

使用标签有一个注意事项——您偶尔需要刷新 TAGS 文件,以防您对代码进行了重大重组。Emacs 不依赖于 TAGS 文件 100% 准确——如果在文件中给定的确切位置未找到标签,它将搜索标签。但是,如果您大量修改了代码,请重新运行 etags 以刷新标签数据库。

另请注意,Emacs 可以一次使用多个 TAGS 文件。大多数基于标签的功能都假设使用当前目录中的文件 TAGS。如果您正在编辑分布在多个目录中的源文件,则可以使用 M-x visit-tags-table 将另一个 TAGS 文件加载到 Emacs 的已知标签列表中。或者,您可以将变量 tags-table-list 设置为可以在其中找到 TAGS 文件的文件或目录列表。例如,我可能希望 Emacs 始终知道在公共库例程中找到的标签。在我的 Emacs 启动文件中,我将使用类似

(setq tags-table-list '("~/lib" "~/src/lib" "~/common"))

的内容。在命名目录中找到的 TAGS 文件将与当前目录中的 TAGS 文件一起使用。

更新变更日志

许多程序都附带一个 ChangeLog,它描述了每天对源代码的更新和修改。Emacs 允许您使用命令 M-x add-change-log-entry(或 C-x 4 a,它将在另一个窗口中执行相同的操作)半自动地更新 ChangeLog。

例如,假设我们正在编辑源文件 grover.c,并且我们向 grover() 添加了一段代码。为了记录此更改,我们使用 C-x 4 a,它将打开一个包含

Sun Jul 24 14:39:50 1994 Matt Welsh (mdw@loomer.debian.org
)
        * grover.c (grover):

的窗口。此命令确定我们位于文件 grover.c 中的函数 grover() 内,并在条目的开头指示这一点。我们现在可以输入新的日志条目并以通常的方式保存文件。您为其添加条目的每个源文件都将获得自己的项目。

编译和调试代码

您可以在 Emacs 中完全编译程序,甚至运行调试器。最基本的编译命令是 M-x compile,它将在当前缓冲区的目录中运行 make(或您选择的另一个命令)。

默认编译命令是 make -k

-k 开关将阻止 make 在对 makefile 中的其他目标没有影响的错误时停止。)当使用 M-x compile 时,系统将提示您输入要使用的编译命令,以及是否要保存任何已更改的缓冲区。如果您想更改默认命令,请将变量 compile-command 设置为另一个值。例如,

(setq compile-command "make")

将导致 M-x compile 在没有 -k 参数的情况下运行 make

您还可以为特定源文件设置 compile-command 的值。这是通过在源文件本身中包含“局部变量定义”来完成的。例如,我们可以在 C 源代码文件中包含以下注释

/* Local Variables: */
/* mode: C */
/* compile-command: "make" */
/* End: */

这些注释为包含此代码的缓冲区设置模式和 compile-command 变量的值。当 Emacs 打开文件时,它会识别包含 Local Variables: 的行,并使用后续行,直到包含 End: 的行,才为仅此缓冲区的变量赋值。您可以使用此功能为特定于此缓冲区的任何 Emacs 变量设置值。

现在,当我们使用 M-x compile 时,Emacs 会在另一个窗口中运行给定的编译命令(此处为 make),我们可以通过该窗口监视编译进度。要终止编译过程,请在编译缓冲区中键入 C-c C-k

编译完成后,我们可以使用打印的错误消息(如果有)自动访问导致错误的源代码。例如,假设我们使用 M-x compile,并且产生以下错误

cd /amd/noon/c/mdw/test/lj/
make
gcc -O -O2 -I/usr/include -I. -c main.c -o main.o
In file included from main.c:12:
libpx.h:30: image.h: No such file or directory
libpx.h:31: misc.h: No such file or directory
make: *** [main.o] Error 1
Compilation exited abnormally with code 1 at Sun Jul 24 16:32:17

您可以将光标移动到编译缓冲区中的错误消息,按 C-c C-c,错误对应的源文件将自动被访问,而不是手动定位 libpx.h 并跳转到有问题的行。然后,您可以更正错误,移动到下一个错误,然后重复。在 X 下,在编译缓冲区中的错误消息上按 鼠标 2 将跳转到相应的源代码行。

如果您有大量错误消息,在编译缓冲区中按 M-n 将移动到下一个错误消息(除了查看相应的源代码)。要使 M-n 在 C 源代码缓冲区中也具有此行为,您可以在 Emacs 启动文件中使用以下命令:(define-key c-mode-map "\M-n" 'next-error

为了简化编译过程,我在我的 Emacs 配置文件中使用以下代码

(defun my-save-and-compile ()
  (interactive "")
  (save-buffer 0)
  (compile "make -k"))
(define-key c-mode-map "\C-c\C-c' 'my-save-and-compile)

这定义了一个新函数 my-save-and-compile,它将自动保存当前缓冲区并运行 make -k。这为我节省了回答仅由 M-x compile 给出的各种提示的麻烦。现在,在 C 模式缓冲区中使用 C-c C-c 将保存源文件并对其进行编译。

一旦您习惯了上述机制,修复错误和重新编译代码就会变得非常轻松——您可以专注于调试,让 Emacs 定位错误、运行 make 等等。

运行 gdb

gdb 是 GNU 调试器。对于用几乎任何编译语言(最值得注意的是 C)编写的程序的运行时调试来说,它是必不可少的。gdb 也可以用于使用 core 文件对崩溃的程序进行事后检查。

毫不奇怪,Emacs 提供了许多功能,允许您在 Emacs 缓冲区中运行 gdb,与相应的源缓冲区交互以查看和编辑代码。虽然 gdb 本身值得一篇教程,但在这里我们将向您介绍特定于 Emacs 的 gdb 功能。gdb 提供了广泛的在线帮助,可以填补此处留下的空白。对于本教程的其余部分,我们假设您基本熟悉 gdb,或类似的调试器,例如 dbx。

让我们采用以下简短程序,该程序无疑会在大多数系统上导致段错误

 #include
int main(void) {
  int i; int *data = NULL;
  data[0] = 1;
  data[1] = 2;
  for (i = 2; i > 30; i++) {
    data[i] = data[i-1] + data[i-2];
  }
  printf("Last value is %d0,data[29]);
}

如您所见,我们正在尝试将数据写入 NULL 指针。果然,当我们编译并运行程序时,我们得到

loomer:~/test/lj/crashme% ./crashme
Segmentation fault (core dumped)

让我们使用 gdb 来检查问题。使用 M-x gdb 会给我们提示

Run gdb (like this): gdb

在这里,您应该完成 gdb 命令行。在这种情况下,我们希望在可执行文件 crashme 上运行 gdb,并使用 core 文件 core。因此,我们完成如下

Run gdb (like this): gdb crashme core

Emacs 应该打开两个窗口——一个包含 gdb 交互会话,另一个包含源文件 crashme.c。gdb 会话将如下所示

GDB 是自由软件,欢迎您在某些条件下分发副本;键入 "show copying 以查看条件。GDB 绝对没有任何保证;键入 "show warranty" 以了解详情。

GDB 4.12,
Copyright 1994 Free Software Foundation, Inc...
Core was generated by "crashme".
Program terminated with signal 11, Segmentation fault.
#0  0x22bc in main () at crashme.c:5
(gdb)

我们现在可以发出 gdb 命令来检查崩溃。立即,我们注意到 crashme.c 缓冲区包含一个箭头,指向当前的源代码行,如下所示

=>data[0] = 1;
  data[1] = 2;
  /* ... */

此箭头不是源代码文本的一部分。它无法选择、修改或删除。您可以自由编辑源缓冲区中的代码;这个假想的箭头不会与编辑后的代码一起保存。箭头的存在只是为了让我们知道 gdb 对当前源代码行的想法。但是请注意,从源缓冲区添加或删除行将导致 gdb 关于源代码行位置的信息与实际代码不同步。

我们可以看到崩溃是由第 5 行的段错误引起的,箭头指向该行。在 gdb 缓冲区中使用 where 命令将为我们提供堆栈跟踪等等。您可以在源缓冲区中更正代码,重新编译,测试和重新运行 gdb(如果需要),所有这些都在 Emacs 中完成。

gdb 也可以用于检查正在运行的程序。例如,我们可以在 gdb 的控制下运行 crashme,并一次单步执行一行。但是,首先,让我们通过将 data 的定义更改为

int data[30];

来更正错误(否则,crashme 会在代码的第一行崩溃,我们将几乎无法继续演示。)

首先,我们应该在代码的第一行设置断点。在 gdb 缓冲区中,我们可以使用 list 显示前几行

 (gdb) list
1       #include <stdio.h>
2       int main(void) {
3         int i;
4         int data[30];
5
6         data[0] = 1;

命令 break 6 将在第 6 行设置断点

(gdb) break 6
Breakpoint 1 at 0x22b0: file crashme.c, line 6.

现在,run 命令将开始执行程序,但会立即在代码的第一行停止。将打开 crashme.c 的缓冲区,我们的友好箭头指向包含断点的行。

现在,我们可以直接使用 gdb 的各种命令,方法是在 gdb 缓冲区中输入它们——或者,我们可以使用 Emacs 快捷键。将光标放在源缓冲区中的代码行上,然后键入 C-x C-a C-b 将在该行设置断点。类似地,C-x C-a C-d 将删除该行上的所有断点。(gdb 命令 info break 将列出当前断点。)设置断点后,您可以使用 C-x C-a C-r 恢复执行。

以上所有命令也可以在 gdb 缓冲区中使用,使用 C-c 而不是 C-x C-a 作为前缀。为了方便起见,在任一缓冲区中使用 C-x SPC 将在当前源代码行设置断点。

如果您发现这些键绑定不必要地冗长,就像我一样,您可能会考虑在 c-mode-map 中重新绑定函数 gud-breakgud-removegud-cont。例如,我使用命令

(define-key c-mode-map "\M-b" 'gud-break)
(define-key c-mode-map "\M-d' 'gud-remove)
(define-key c-mode-map "\M-r" 'gud-cont)

当然,这否定了 M-bM-dM-r 在 C 模式下的先前含义。

以下附加宏在 gdb 缓冲区以及 C 模式下可用,方法是将前缀从 C-c 更改为 C-x C-a

  • `C-c C-s' 单步执行一行代码,进入函数调用。(gdb step 命令。)

  • `C-c C-n' 单步执行一行代码,不进入函数调用。(gdb next 命令。)

  • `C-c <' 上移一个堆栈帧。(gdb up 命令。)

  • `C-c >' 下移一个堆栈帧。(gdb down 命令。)

  • `C-c C-f' 运行到当前函数完成,然后停止。(gdb finish 命令。)

同样,您可能希望将这些绑定到较短的键序列(例如 M-sM-n 等)。

另一个有趣的命令是 C-x C-a C-p,它(在源缓冲区中)将获取光标周围的 C 表达式并将其传递给 gdb 的 print 命令,该命令评估表达式并打印其值。这是一种非常方便的方式来检查调试器中的变量、数据结构等等。如果您在 gdb 中执行调试程序,您甚至可以使用此命令调用函数并打印返回值。

例如,将光标放在行

printf("Last value is %d\n",data[19]);
and pressing C-x C-a C-p will cause the following to be printed in the
gdb buffer:
(gdb)
$1 = 16

在这种情况下,data[19] 为 0,因为我们尚未执行计算循环。尽管如此,我们可以调用程序中的函数(或者,实际上,使用 print 命令手动调用任何任意函数)并检查返回值。

Emacs 还允许您定义自己的函数来与调试器交互。例如,我们可能希望将光标移动到代码行,并运行 gdb until 函数,这将导致执行继续,直到到达该行为止。

这是通过 gud-def 函数完成的。例如,我们可以使用(gud-def my-until-line "until %f:%l""\C-u" "运行到当前行。"

这将定义函数 my-until-line,它将字符串

until %f:%l

发送到 gdb 进程,其中 %f 替换为当前的源文件名,%l 替换为当前的行号。新函数将绑定到键序列 C-x C-a C-u(在源缓冲区中)和 C-c C-u(在 gdb 缓冲区中)。最后一个参数是命令的文档字符串,使用 describe-function 打印。

现在,我们可以将光标放在源缓冲区中的代码行上,键入 C-x C-a C-u,执行将继续,直到到达该行代码为止。

我们可以通过另一种方式自定义与调试器的交互。例如,gdb 缺乏自动单步执行代码的固有能力,从而使我们可以在不中断的情况下监视程序的执行。通过连续多次使用 step 命令可以实现类似的效果,但我们希望 Emacs 为我们自动化该过程。

这可以使用以下函数完成

(defun gdb-step-forever (arg)
  (interactive "NTime between steps: ")
  (while -t
    (progn
      (sit-for arg)
      (gud-step 1))))

M-x gdb-step-forever 运行此函数将提示我们输入步进之间睡眠的时间量(以秒为单位)。(这不必是秒的整数 - 您可以指定诸如 0.5 之类的实数值。)然后,该函数将暂停给定的时间量,运行 gud-step,然后无限期重复。要中断该函数,您可以使用 Emacs 退出键 C-g

此想法的更一般的推论将允许我们在步进之间运行“hook”函数,这将允许我们打印变量的值等等。

请注意,上面的函数不是很智能——如果它遇到断点,或者程序由于某种原因停止执行,它将继续天真地循环。在这些情况下,您可以简单地手动中断该函数。

鉴于上述导览,您应该准备好解决 Emacs 提供的其他编程功能——包括版本控制、自定义缩进样式等等。也许未来一期的Linux Journal 将涵盖 Emacs 的这些方面。

我欢迎对此处介绍的材料的所有建议、意见和更正。请随时将信件发送给作者,转交 Linux Journal,或通过电子邮件发送至 mdw@sunsite.unc.edu

祝你编程愉快!

Matt Welsh (mdw@sunsite.unc.edu) 是康奈尔大学机器人与视觉实验室的程序员。他是一位作家和程序员,几乎完全使用 vi——也许更多的是偶然而非设计。他将空闲时间花在自酿虚拟啤酒和演奏布鲁斯音乐上。

加载 Disqus 评论