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

经过这几节文章的介绍,相信读者应该发现了:虽然指针是C语言中相对比较难的语法,但它也是C语言非常吸引人的点。指针的用途太多,以至于从初学者的角度来看,甚至显得有些“难以捉摸”了。

用途广泛的指针

请看下面这个C语言程序:

#include <stdio.h>

char* strA()
{
    char str[] = "hello world";
    return str;
}
int main()
{
    printf("%s\n", strA());
    return 0;
}


这段C语言代码出自美国某著名硬盘S公司的面试题,程序会输出什么呢?

乍一看,答案似乎很明显,strA() 函数返回的是经典的“hello world”,那这段C程序也就会输出此了?然而事与愿违,我们实际编译执行这段C语言代码,发现:

# gcc t1.c 
# ./a.out 

#

输出为空!不过这一切都在预计中,在第 5 节我们讨论过类似的问题:str 只是 strA() 函数的局部变量,在 strA() 函数执行完毕的时候,它就被释放了。因此在 main() 中调用 strA() 函数,得到的返回值肯定是未定义的,因此最终输出什么也不会觉得奇怪。现在我们把 strA() 函数做如下修改:

char* strA()
{
    char* str = "hello world";
    return str;
}

再编译修改后的C语言代码并执行,结果如下:

# gcc t1.c 
# ./a.out 
hello world
#

输出正常了,怎么回事?str 仍然是局部变量啊,难道就因为它是指针,所以就没有被释放?指针还有这个特性?当然不是。还记得第5节文章中的这张图吗?

红框部分的内存区域会一直持续到整个C语言程序退出。再来看 char* str = "hello world";,显然,strA() 函数并没有在自己的栈区分配内存存储"hello world",仅分配了指针 str 指向 "hello world",而该字符串被存放在红框内的只读数据段。这就明白了,main() 中 printf() 输出的字符串其实是来自上图红框中的内存段,而不是 strA() 的栈区。

上面提到的其实是指针比较简单的用法,第9节还介绍了C语言中复杂指针的分析方法,相信看了文章的朋友应该明白如何定义各种复杂的指针数据类型。

在第10节,我们又分析了C语言中的二维数组在内存中的存储方式,在此基础上讨论了C语言中数组指针,并做了一道美国著名软件公司的一道相关面试题。

再来道面试题练练手

为了加深对C语言中较复杂指针的理解,请读者再来看看下面这道题,C语言代码如下:

#include <stdio.h>

int main()
{
     int a[] = {1, 2, 3, 4, 5};
     int *ptr = (int*)(&a + 1);
     printf("%d %d\n", *(a+1), *(ptr-1));

     return 0;
}


上面这段C语言代码出自美国某著名CPU生产公司的面试题,这个C程序会输出什么呢?显然这是一道考察指针的题目。得到答案最简单粗暴的方法就是实际编译并执行这段C语言代码,请看:

# gcc t.c
# ./a.out 
2 5

分析

答案是2和5,为什么呢?第一个数字好分析,*(a+1) 是指针的简单用法,等于 2 没什么好说的。这道题的难点就在于 int 型指针 ptr 的指向,它指向的 (&a + 1) 表示什么含义呢?

要想知道 &a + 1 的含义,分析出 &a 的含义就可以了,“+1”无非就是指针的移动,第 10 节已讨论的比较清楚。

a 是一个 int 型数组,它本身就相当于是一个“只读类型”的指针,再在 a 前加上一个取地址符 &,显然就成了双指针,那这个双指针有什么含义呢?首先应该明白的是,虽然在编写C语言代码时,没有指定数组 a 的元素个数,但是在编译阶段,编译器还是会根据 a 的初始化信息确定 a 的元素个数。也就是说:

int a[] = {1, 2, 3, 4, 5};
// 就相当于
int a[5] = {1, 2, 3, 4, 5};

在C语言中声明一个 int 型变量非常简单:

int v;


观察变量 v 的数据类型时,我们把 v 遮住,"int v"仅余下 int,因此 v 是 int 类型的。同样,假如我们把“int 数组”整体看作是一种数据类型,那么显然变量 a 就是 int [5] 类型的。

现在对变量 v 取地址,int* pv = &v,显然 pv 就是 int 型指针。相应的,对变量 a 取地址,(int [5])* ptr = &a,显然 ptr 就是 int [5] 类型的指针。C语言当然没有 int [5] 类型,int [5] 只是方便我们理解 &a 含义的。

事实上,第 9 节最后对 int [5] “类型”已经解释的比较详细,感到陌生的朋友可以再翻回去看一看。现在将 ptr 严格对应到 C语言语法,其实就相当于:

int (*ptr)[5];

到这里就一切清楚了,指针 ptr 其实就是一个数组指针。上面的面试题给出的C语言代码是:

int *ptr = (int*)(&a + 1);

其实就是多了指针的移动和强制转换的步骤而已,&a 的含义仍然相当于是一个数组指针,因此 &a+1 就相当于 (int* )((char* )a + sizeof(a)),也就是说 ptr 此时指向的是 a[5],那么 ptr-1 显然就指向 a[4] 了,也就是 5。

小结

可以看出,只要不把C语言指针特殊化对待,而是把它与 int 等普通数据类型对比来看,一切就很简单了。在分析复杂指针时,可以尝试使用本系列文章原创的“遮挡分析法”,相信会有所帮助的。

阅读更多:   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