指针是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 再试一试。