C语言面试题详解(第6节)

指针是C语言的灵魂,或者换句话说,离开了指针,C语言就只能处理 1+1 等于 2 这样的小学问题了。这么说的确有些夸张,不过读者应该能明白这只是为了说明指针在C语言中非常重要

不要神话C语言指针

“指针很重要”,相信很多C语言初学者早就有所耳闻,在学习时严阵以待。但有些朋友可能太过于紧张,在学习过程中甚至“神话了指针”,下面是一个典型的例子:

void setp(int *p)
{
    p = &global;
}
int *ptr;
setp(ptr);

请看上面的C语言代码,在之前的文章中,为了解释 setp() 函数无法改变 ptr,给出了下面这样的C语言代码,请看:

void seti(int a)
{
    a = 3;
}
int ai;
seti(ai);

seti() 函数的行为和 setp() 函数的行为是非常相似的,我曾认为:如果读者知道 seti() 函数无法修改 ai 的值,也自然就明白为什么 setp() 函数无法修改 ptr 了。可是有朋友回复说,“setp() 函数的参数不一样,这可是指针!

其实有何不同呢?把 int * 看作是C语言中和 int 一样的基础数据类型,一切不就都好理解了吗?接下来几节,将讨论几道关于指针的面试题目,希望在讨论这些题目的过程中,能够帮助读者加深对C语言指针的认识。

我们并不是为了做题而做题,而是为了巩固基础,学习知识才去做题的,对不?

来看看这道C语言面试题

下面这道C语言题目来自美国某著名软件企业M公司的面试题,这个C语言程序肯定无法正常运行,那么它会在哪一行崩溃呢?以及为什么会崩溃呢?

struct S{
     long     i;
     long     *p;
};  
int main()
{
     struct S s;
     long *p = &s.i;
     p[0] = 4;
     p[1] = 3;
     s.p=p; 
     s.p[1] = 1;
     s.p[0] = 2;

     return 0;
} 

初步分析

先来分析一下C语言源代码:p 是一个 long 型的指针,它指向结构体 s 的 i 成员,接着分别对 p[0] 和 p[1] 赋值 4 和 3。然后又让结构体 s 的 p 成员指向 p,对 s.p[1] 和 s.p[0] 分别赋值为 1 和 2。代码很简单,那究竟哪里会崩溃呢?

在之前几节中,我们得到答案最简单粗暴的方法是将C语言代码编译,并在计算机中运行。可是这一题的C语言程序无法正常运行,而是会崩溃:

# gcc t.c 
# ./a.out 
Segmentation fault

这就没有办法通过直接运行C语言程序的方式得到答案了吗?也不是,我们至少还有两种方法。第一种方法就是在每一行之后插入 printf() 函数,使之输出些内容,对应的C语言程序可以如下修改:

int main()
{
     struct S s;
     long *p = &s.i;
     p[0] = 4;
     printf(" -- 1\n");
     p[1] = 3;
     printf(" -- 2\n");
     s.p=p; 
     printf(" -- 3\n");
     s.p[1] = 1;
     printf(" -- 4\n");
     s.p[0] = 2;
     printf(" -- 5\n");
     return 0;
} 

因为用到了 printf() 函数,所以不要忘了包含 stdio.h 头文件。

这样一来,C语言程序会执行崩溃之前的 printf() 语句,通过输出的内容,我们就能推断出程序是从哪里崩溃的了,请看:

# gcc t.c 
# ./a.out 
-- 1
-- 2
-- 3
-- 4
Segmentation fault

答案已经非常明显了,上面的C语言程序在执行 s.p[0] = 2 这一行时崩溃了,那为什么会在这一行崩溃呢?请继续往下看。

“第二种方法”就是借助 gdb 的单步调试法了,不过这里讨论的是 C语言,就不啰嗦 gdb 了,感兴趣的朋友可以翻翻我之前的文章。

进一步分析

再分析一下C语言代码,p 指向的是 s.i,而 i 是结构体 s 的第一个成员,所以 p 实际上指向的也是结构体 p,那么 p+1 指向的就是 s.p 了,此时

p[0] = 4;
p[1] = 3;
// 就相当于执行
s.i = 4;
s.p = 3;


这样程序当然不会崩溃。既如此,我们继续往下分析:执行 s.p = p; 后,其实就是让 s.p 指向 s.i,也即指向结构体 s 本身,那么

s.p[1] = 1;
// 就相当于执行
s.p = 1;

这时还不会出错,只不过这一句执行完毕后,s.p 就执行地址 1 了。此时

s.p[0] = 2;
//就相当于执行
*((int*)1) = 2;

对地址 1 赋值,肯定会引发段错误,导致C语言程序崩溃。至此,我们就明白

再来看看这个C语言代码

现在将上述C语言代码的 long 修改为 int:

struct S{
     int     i;
     int     *p;
};  
int main()
{
     struct S s;
     int *p = &s.i;
     p[0] = 4;
     p[1] = 3;
     s.p=p; 
     s.p[1] = 1;
     s.p[0] = 2;

     return 0;
}


此时再编译运行,发现C语言程序可以正常运行,并没有崩溃。这是怎么回事?要回答这个问题,就需要了解结构体成员的字节对齐了,限于篇幅,以后有机会再说了。

我的机器是 64 位的,如果读者使用的机器是 32 位的,即使将 long 修改为 int,C语言程序可能仍然会崩溃。此时,读者可以尝试将 int 改为 short 再试一试。
阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif