什么让你对C/C++如此恐惧?
晦涩的语法?还是优秀IDE的欠缺?
我想那都不是问题,最多的可能是一个类似这样的错误:
段错误(Segmentation fault)
这是新手无法避免的错误,也是老手极力回避也经常遇到的错误。
本篇,试图简略地剖析一段会引发这个错误的程序,带来一些启发。
先看两份代码,一份是错误的.
错误代码
<code class="language-c hljs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#include "string.h"</span> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#include <stdlib.h></span> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#include <stdio.h></span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> func1(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> ** dest,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> * src,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> n) { (*dest) = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span>*)<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">malloc</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">sizeof</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span>)*n); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">strcpy</span>(*dest,src); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> main(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> argc,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span>** args) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> ** p = NULL; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> str[] = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"foreach_break"</span>; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> len = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">sizeof</span>(str); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">printf</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"%d\n"</span>,len); func1(p,str,len); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">printf</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"%s\n"</span>,*p); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">free</span>(p); p = NULL; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>
正确代码
<code class="language-c hljs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#include "stdio.h"</span> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#include "string.h"</span> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#include "stdlib.h"</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> func1(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> ** dest,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> * src,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> n) { (*dest) = (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span>*)<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">malloc</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">sizeof</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span>)*n); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">strcpy</span>(*dest,src); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> main(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> argc,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span>** args) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> * p = NULL; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> str[] = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"foreach_break"</span>; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> len = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">sizeof</span>(str); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">printf</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"%d\n"</span>,len); func1(&p,str,len); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">printf</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"%s\n"</span>,p); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">free</span>(p); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//p = NULL;</span> }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul>
代码意图来自技术问答中的一个huffman树不能运行的问题。
当然,我剥离掉了大部分关于huffman的部分,并稍加改动。
它们最大的不同:
错误代码:
<code class="hljs rust has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> ** p = NULL; func1(p,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">str</span>,len);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
正确代码:
<code class="hljs rust has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> * p = NULL; func1(&p,<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">str</span>,len);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
也许你会奇怪,你看到“正确”的代码中居然注释了这行:
<code class="language-c hljs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//p = NULL;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
参数传递
同时,可能有人会觉得指针char ** p
向函数func1传递*p
,与指针char * p
向函数func1
传递&p
没什么不同啊?
嗯。这种想法也很有惯性,因为C语言没有类似这样的函数声明:
void func(int &)
同时,对char * p
作&p
操作不也得到个char **
吗?
运行程序
那么,我们看看程序自己怎么说?
如果你经常遇到段错误,希望你仔细看明白上面的图在说什么。
野指针
所谓野指针,就是很野的指针,你不知道它指向了哪个地址,也不知道对这个地址取值是否会出错,但,野指针也是指针,有一个存放它的内存地址.
正确的代码中,存放指针
p
的地址是0x7fffffffddc0
;
错误的代码中,存放指针p
的地址是0x7fffffffdd78
;
零指针
所谓零指针,就是指向了0x0的指针.对这个0x0取值是否会出错呢?你想一想.
悬浮指针
你对于在正确的程序中注释了p = NULL
而感到不解?
其实这没什么,取决于这个指针p
在后续代码中怎么使用.
<code class="hljs scss has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-function" style="box-sizing: border-box;">free(p)</span>;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
这句代码的执行,会释放掉指针p
所指向的内存地址,归还给操作系统.
当然前提是这个地址确有所指、你也有权访问它.
<code class="hljs fix has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-attribute" style="box-sizing: border-box;">p </span>=<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;"> NULL;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
这句代码的执行,是让指针p
指向了0x0
,变回了空指针.
虽然它指向的内存已经被释放,但是它还指向那个地址.
这就是悬浮指针,指向的地址已经不可用确还指向,就不是确有所指.
由于我们的main
函数即将执行完毕,所以在它返回后,存放空指针p
的地址会被释放.
因为指针
p
是main
函数的一个临时变量.
所以我们可以毫无顾虑的注释掉p = NULL
.
内存泄露
另外,如果free(p)
没有被执行,而先执行了p = NULL
,那么p
原来指向的内存空间可能就无法被正确释放,如果再也没有其它的引用指向了那块地址,那块地址就被遗忘在那里,同时不能被回收.
这个,叫内存泄露 (Memory Leak).
接着看程序
现在,我们来看看函数func1
的调用。
首先,是C代码:
然后是两段代码的对比:
你应该已经看出了差别。
错误的代码的dest
参数传入了0x0
.
接着是执行:
<code class="hljs delphi has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">(*dest) = (char*)</span>malloc(sizeof(char)*n);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
这句代码在进行(*dest)
时就会发生段错误.
究其原因,就在于char ** p = NULL
让p
变成了零指针,*p
相当于对0x0
这个地址取值.
应用程序启动时,操作系统会建立一个进程(process),这个进程拥有自己独立的地址空间,称作虚拟地址空间(virtual memory space).
0x0
在这个空间中,不能被访问.
试图访问一个不能被访问的空间,就会段错误.
总结
段错误的一种,我们探索完毕.
现在你知道以下两种操作的含义了吗?
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* p指向的地址,对其取值,如果这个地址有东西,也有权取,没问题.*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> ** p -> *p; (<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* 存放p的地址,或者指向p的引用,这个地址必然有东西,所以没问题*/</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> * p -> &p (<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
本篇结束.