📄 freebsd 开发手册.html
字号:
</div><p>或者给你的程序选一个更好的名字!</p></div></div><div class="QANDAENTRY"><div class="QUESTION"><p><a id="Q2.4.1.7." name="Q2.4.1.7."></a><b>2.4.1.7.</b>我编译了一个程序,开始看起来运行得不错。但是后来调试了,说什么 “<ttclass="ERRORNAME">core dumped</tt>”。这个是什么意思?</p></div><div class="ANSWER"><p><b></b><i class="FIRSTTERM">core dump</i> 这个名字可以追溯到 <spanclass="TRADEMARK">UNIX</span> 的早期历史,当时的计算机都使用线圈内存储存数据。通常情况下,如果一个程序在一定的情况下执行失败了,系统就会把线圈内存的内容写到磁盘上的一个文 件中,这个文件就叫<tt class="FILENAME">core</tt>。通过研究这个文件,程序 员就可以发现问题之所在。</p></div></div><div class="QANDAENTRY"><div class="QUESTION"><p><a id="Q2.4.1.8." name="Q2.4.1.8."></a><b>2.4.1.8.</b>挺不错,但现在我该怎么办呢?</p></div><div class="ANSWER"><p><b></b>使用 <tt class="COMMAND">gdb</tt> 分析这个 core 文件 (见 <ahref="#DEBUGGING">第 2.6 节</a>)。</p></div></div><div class="QANDAENTRY"><div class="QUESTION"><p><a id="Q2.4.1.9." name="Q2.4.1.9."></a><b>2.4.1.9.</b> 我的程序把 core dump以后,说有一个什么 “<tt class="ERRORNAME">segmentation fault</tt>”。这是什么?</p></div><div class="ANSWER"><p><b></b>基本上是你的程序尝试对内存进行某种非法的操作导致的。<spanclass="TRADEMARK">UNIX</span> 在设计上要保护操作系统本身和其他程序不受非法程序的干扰。</p><p>通常的原因有如下这些:</p><ul><li><p>尝试赋值给一个 <code class="SYMBOL">NULL</code> 指针,例如</p><pre class="PROGRAMLISTING">char *foo = NULL;strcpy(foo, "bang!"); </pre></li><li><p>使用一个未被初始化的指针,例如</p><pre class="PROGRAMLISTING">char *foo;strcpy(foo, "bang!"); </pre><p>在某种情况下,指针所包含的值会指向内存中某个区域,这个区域对你的程序是不可操作的。在你的程序造成任何破坏之前,内核会终止程序。如果你运气不好,这个指针会指向你自己的程序在内存中的某个区域,从而破坏自身的某些数据结构,导致程序奇怪地崩溃。</p></li><li><p>数组越界,例如</p><pre class="PROGRAMLISTING">int bar[20];bar[27] = 6; </pre></li><li><p>尝试在只读内存中储存数据,例如</p><pre class="PROGRAMLISTING">char *foo = "My string";strcpy(foo, "bang!"); </pre><p><span class="TRADEMARK">UNIX</span> 编译器经常把像 <tt class="LITERAL">"Mystring"</tt> 这样 的字符串放到只读内存中。</p></li><li><p>错误的使用函数 <code class="FUNCTION">malloc()</code> 和 <codeclass="FUNCTION">free()</code>,例如</p><pre class="PROGRAMLISTING">char bar[80];free(bar); </pre><p>or</p><pre class="PROGRAMLISTING">char *foo = malloc(27);free(foo);free(foo); </pre></li></ul><p>这些错误并不总会导致你的程序崩溃,但这些都是坏的习惯。有些系统和编译器比其他的系统和编译器有更多的容错性,这就是为什么一些程序在一个系统上可以运行很好,而在另一个系统上却会崩溃。</p></div></div><div class="QANDAENTRY"><div class="QUESTION"><p><a id="Q2.4.1.10." name="Q2.4.1.10."></a><b>2.4.1.10.</b> 有时候当我得到一个 coredump,提示说 “<tt class="ERRORNAME">bus error</tt>”。我的 <spanclass="TRADEMARK">UNIX</span> 教材里面说这意味这硬件错误,但是计算机看起来运行很正常。这是真的吗?</p></div><div class="ANSWER"><p><b></b>不是真的,很幸运不是(除非你真的遇见了一个硬件问题...)。这通常是用另一种方式说你尝试读写一块无权读写的内存。</p></div></div><div class="QANDAENTRY"><div class="QUESTION"><p><a id="Q2.4.1.11." name="Q2.4.1.11."></a><b>2.4.1.11.</b> 如果我可以让 core dump在需要的时候产生,那就真的很不错。我能 这样做吗,或者我得等直到发生一个错误?</p></div><div class="ANSWER"><p><b></b>可以,切换到另一个控制台或者起动 xterm, 执行</p><pre class="SCREEN"><samp class="PROMPT">%</samp> <kbd class="USERINPUT">ps</kbd> </pre><p>找到你的程序的进程号,然后执行</p><pre class="SCREEN"><samp class="PROMPT">%</samp> <kbd class="USERINPUT">kill -ABRT <ttclass="REPLACEABLE"><i>pid</i></tt></kbd> </pre><p>其中的 <code class="PARAMETER"><tt class="REPLACEABLE"><i>pid</i></tt></code>就是你找到的进程号。</p><p>如果你的程序陷入了一个无限循环,这样做就很有用处。如果你的程序 偶然得到了 <codeclass="SYMBOL">SIGABRT</code> 信号,还有一些类似的信号也有同样 的功用。</p><p>或者,你可以使用函数 <code class="FUNCTION">abort()</code> 在自己的程序 中产生一个core dump。请参考手册的 <ahref="http://www.FreeBSD.org/cgi/man.cgi?query=abort&sektion=3"><spanclass="CITEREFENTRY"><span class="REFENTRYTITLE">abort</span>(3)</span></a>来了解更多。</p><p>如果你想在自己的程序之外产生一个 core dump,而不让程序终止, 那么你可以用命令 <ttclass="COMMAND">gcore</tt>。请参考手册的 <ahref="http://www.FreeBSD.org/cgi/man.cgi?query=gcore&sektion=1"><spanclass="CITEREFENTRY"><span class="REFENTRYTITLE">gcore</span>(1)</span></a>了解更多。</p></div></div></div></div></div><div class="SECT1"><hr /><h2 class="SECT1"><a id="TOOLS-MAKE" name="TOOLS-MAKE">2.5 Make</a></h2><div class="SECT2"><h3 class="SECT2"><a id="AEN742" name="AEN742">2.5.1 什么是 <ttclass="COMMAND">make</tt>?</a></h3><p> 当你写一个简单的程序,只有一到两个源文件的时候,输入</p><pre class="SCREEN"><samp class="PROMPT">%</samp> <kbd class="USERINPUT">cc file1.c file2.c</kbd></pre><p> 就没什么问题,但如果有很多源文件就会很烦人──编译的时间也会很长。</p><p> 一个方法就是使用目标文件,只在源文件有改变的情况下才重新编译源文件。因此你可以这样做:</p><pre class="SCREEN"><samp class="PROMPT">%</samp> <kbd class="USERINPUT">cc file1.o file2.o</kbd> ... <kbdclass="USERINPUT">file37.c</kbd> ...</pre><p> 上次编译后,<tt class="FILENAME">file37.c</tt> 发生了改变,但其他文件没有。这样做可以让编译过程快很多,但是也不能解决累人的输入问题。</p><p> 或者我们可以使用一个 shell script 来解决输入问题,但是也需要重新编译所有文件,在大型项目上很没有效率。</p><p> 如果有成百上千的源文件的话怎么办?如果我们在与很多人合作写程序,别人对源文件进行了修改,又没有告诉你,该怎么办?</p><p> 也许我们可以把以上两种方法结合,写一种像 shell script 一样的东西。这种文件包含某种技巧可以决定什么时候该对源文件进行编译。现在所有我们要的就是一个程序可以懂得这种技巧,因为要懂得这种技巧,shell 还没那么大的能耐。</p><p> 这个程序就叫 <tt class="COMMAND">make</tt>。它读入一个文件,叫 <iclass="FIRSTTERM">makefile</i>,这个文件决定了源文件之间的依赖关系。而且决定了源文件什么时候该编译什么时候不应该编译。例如,某个规则可以说 “ 如果 <ttclass="FILENAME">fromboz.o</tt> 比 <tt class="FILENAME">fromboz.c</tt> 要旧,意思就是有人修改了 <tt class="FILENAME">fromboz.c</tt>,因此我们需要重新编译这个文件。”这个 makefile 还有规则通知 make 该 <span class="emphasis"><bclass="EMPHASIS">怎么</b></span> 重新编译源文件,因此 make 是一个强大得多的工具。</p><p> makefile 通常和相关的源文件保存在同一个目录下,可以叫做 <ttclass="FILENAME">makefile</tt>,<tt class="FILENAME">Makefile</tt> 或者 <ttclass="FILENAME">MAKEFILE</tt>。大多数程序员会使用 <tt class="FILENAME">Makefile</tt>这个名字,因为这样可以让这个文件被放在目录列 表的顶端,可以很容易得看见。 <a id="AEN773"name="AEN773" href="#FTN.AEN773"><span class="footnote">[6]</span></a></p></div><div class="SECT2"><hr /><h3 class="SECT2"><a id="AEN777" name="AEN777">2.5.2 使用 <tt class="COMMAND">make</tt>的例子</a></h3><p> 这是一个非常简单的 make 文件:</p><pre class="PROGRAMLISTING">foo: foo.c cc -o foo foo.c</pre><p> 包含两行,一行是依赖关系,一行是执行动作。</p><p> 依赖关系的那一行包含了程序的名字 (叫做 <iclass="FIRSTTERM">target</i>),紧跟着一个冒号,然后是空格,最后是源文件的 名字。当 <ttclass="COMMAND">make</tt>读入这一行的时候,会检查 <tt class="FILENAME">foo</tt>是否存在;如果存在,就比较 <tt class="FILENAME">foo</tt> 和 <ttclass="FILENAME">foo.c</tt> 最后的修改时间有什 么不同。如果 <tt class="FILENAME">foo</tt>不存在,或者比 <tt class="FILENAME">foo.c</tt>要旧,就检查执行动作那一行看看该怎么做。换句话 说,就是 <tt class="FILENAME">foo.c</tt>需要重新编译的时候该怎么办。</p><p> 执行动作那一行以一个 <span class="TOKEN">tab</span> (按下 <bclass="KEYCAP">tab</b>) 开始,然后是你在命令行下产生 <tt class="FILENAME">foo</tt>所执行的命令。如果 <tt class="FILENAME">foo</tt> 过期了,或者不存在,<ttclass="COMMAND">make</tt> 就会 执行这个命令来产生 <ttclass="FILENAME">foo</tt>。换句话说,这就是重新编译 <tt class="FILENAME">foo.c</tt>的规则。</p><p> 因此,当你输入 <kbd class="USERINPUT">make</kbd> 时,它会确定 <ttclass="FILENAME">foo</tt> 和 <tt class="FILENAME">foo.c</tt> 在修改时间上是否同步。这个原则可以在 <tt class="FILENAME">Makefile</tt> 里扩展到成百上千的目标文件上──实际上,在 FreeBSD 里,你只要在合适的目录下输入 <kbd class="USERINPUT">makeworld</kbd> 就可以编译整个操作系统!</p><p> makefile 另一个有用的特点就是目标文件不一定就是程序。例如,我们可以 有这样的 make文件。</p><pre class="PROGRAMLISTING">foo: foo.c cc -o foo foo.cinstall: cp foo /home/me</pre><p> 我们可以输入如下的命令告诉 make 该执行哪个目标:</p><pre class="SCREEN"><samp class="PROMPT">%</samp> <kbd class="USERINPUT">make <ttclass="REPLACEABLE"><i>target</i></tt></kbd></pre><p> <tt class="COMMAND">make</tt> 会只执行这个目标而忽略其他的目标。例如,如果我们输入 <kbd class="USERINPUT">make foo</kbd>,就只有 <tt class="MAKETARGET">foo</tt>被执行,必要的情况下重新编译 <tt class="FILENAME">foo</tt> 而不会继续执行 <ttclass="MAKETARGET">install</tt> 这个目标。</p><p> 如果我们只是输入 <kbd class="USERINPUT">make</kbd> 这个命令,make 总会寻找第一个目标,并且在执行完以后就不管其他的目标了。例如,如果我们输入 <kbdclass="USERINPUT">make foo</kbd>,make 就会转到 <tt class="MAKETARGET">foo</tt>这个目标,在必要的情况下重新编译 <tt class="FILENAME">foo</tt>,而不会执行 <ttclass="MAKETARGET">install</tt> 目标, 然后就停止了。</p><p> 一定要注意,<tt class="MAKETARGET">install</tt> 这个目标不依赖任何其他的东西!这意味着我们一旦输入 <kbd class="USERINPUT">make install</kbd>,这个目标下的所有命令都将被执行。这种情况下,<tt class="FILENAME">foo</tt> 将被安装到用户的家目录下。应用程序的 makefile 正是这样写的,以便程序在正确编译后可以被安装到正确的目录。</p><p> 要尝试解释的话会比较容易让人混淆。如果你不太懂 <tt class="COMMAND">make</tt>是如何工作的,最好的办法就是先写一个简单的程序例如 “hello world” 以及和上面的例子相同的make 文件再去实验。然后 再进一步,使用多个源文件,或者让你的源文件包含一个头文件。 <ttclass="COMMAND">touch</tt> 命令在这里就非常有用了──它能让在不改变文件内容的情况下改变文件的日期。</p></div><div class="SECT2"><hr /><h3 class="SECT2"><a id="AEN833" name="AEN833">2.5.3 Make 和 include-文件</a></h3><p> C 源码的开头经常有一系列被包含的头文件,例如 stdio.h。有一些是系统级的头文件,有一些是你正在写的项目的头文件:</p><pre class="PROGRAMLISTING">#include <stdio.h>#include "foo.h"int main(....</pre><p> 要确定在你的 <tt class="FILENAME">foo.h</tt> 被改变之后,这个文件也会被重新编译,就要在你的 <tt class="FILENAME">Makefile</tt> 这样写:</p><pre class="PROGRAMLISTING">foo: foo.c foo.h</pre><p> 当你的项目变得越来越大,你自己的头文件越来越多的时候,要追踪所有这些头文件和所有依赖它的文件会是一件痛苦的事情。如果你改变了其中一个头文件,却忘了重新编译所有依赖
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -