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

本系列文章主要分析国内外知名公司的C语言面试题,但目的绝不仅仅只是为了做题,而是为了检查自己的知识欠缺点,巩固自己的内功。

这系列文章已经发布了 4 篇,每一篇文章都有朋友回复说讨论这些“偏题”和“怪题”没有意义,“要是写出这样的代码,早就被开除了”。可是正如我之前所说的一样,所谓的“偏”和“怪”,换个角度来看,也许就只是“比较注重基础”而已

似乎咱们国内很多程序员都不太注重基础,可是基础不牢,怎么能建高楼呢?

事实上,本系列文章讨论的C语言题目,都是来自国内外优秀的知名企业的面试题或者笔试题。如果朋友们能够静下心来琢磨琢磨,相信对自己的技术提升还是非常有帮助的。

本节来看看这个题目

下面这道题目来自美国某著名计算机嵌入式公司的面试题,请看如下 C语言代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void GetMemory(char *p, int num)
{
     p = (char*)malloc(sizeof(char)*num);
 }
int main()
{
    char *str = NULL;
    GetMemory(str, 100);
    strcpy(str, "hello");
    printf("str: %s\n", str);
    return 0;
}


上面这个 C语言程序会输出什么呢?当然了,得到这道题目答案最简单粗暴的方法就是将该C语言代码写出,并且编译运行一次。请看:

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


显然,这个C语言程序没能正确运行,而是崩溃,输出了“Segmentation fault”错误信息。

初步分析

相信没有人会故意写出无法运行的C语言代码,该程序员的初衷应该是: 通过 GetMemory() 函数从堆中分配 num 个字节的内存空间,并且把这段内存的首地址通过参数 p 传出。

在 main() 中,他预想 str 会指向系统分配给程序的 100 字节内存段,此时将字符串“hello”拷贝给 str 自然没有问题,程序最终会输出:

str: hello

然而事与愿违。那么,这到底是怎么回事呢?这其实就是一道非常基础的题目,相信有朋友应该能够看出,这道题目其实就是下面这个问题的变形:

#include <stdio.h>

void set(int a)
{
    a = 8;
}
int main()
{
    int a = 3;
    set(a);
    printf("a=%d\n", a);
    return 0;
}


即使 C语言初学者也知道,set() 函数并没有影响到 main() 函数里的变量 a,以上C语言程序输出 "a=3"。我们对上面的面试题C语言程序稍作修改:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char* ptr;
void GetMemory(ptr p, int num)
{
     p = (ptr)malloc(sizeof(char)*num);
 }
int main()
{
    ptr str = NULL;
    GetMemory(str, 100);
    strcpy(str, "hello");
    printf("str: %s\n", str);
    return 0;
}


这样看来就清楚了,GetMemory() 函数分配的内存段并没有返回给 main() 函数里的 str,在将字符串“hello”拷贝给 str 时,实际上是将其拷贝给 NULL,最终C语言程序自然崩溃,引发“Segmentation fault”。

进一步分析

那么,该如何修改,才能使C语言程序按照该程序员的初衷运行呢?要回答这个问题,首先要弄明白为什么 GetMemory() 函数分配的内存并没有通过其参数返回给 main() 函数里的 str。

或者说,为什么 set() 函数没有影响 main() 函数里的变量 a。

相信大家都知道,C语言程序运行在内存中。实际上,系统分配给程序的内存可以划分为好几个段,每发生一次函数调用,系统就会从栈划分出一小片区域(即所谓的“栈帧”)给函数使用。请看下图:

因为这里的C语言程序是从 main() 函数开始运行的,所以一开始系统就要从栈中分配一块栈帧(上图的浅绿色区域)给 main() 函数。接着,main() 函数调用了 GetMemory() 函数,所以系统又分配一块栈帧(上图的橘黄色区域)给 GetMemory() 函数使用。

函数的局部变量都存在于自己的栈帧中,而函数的参数也是它自己的局部变量,所以 GetMemory() 函数中的参数 p 实际上存在于橘黄色内存段中,而 main() 函数中的 str 则存在于浅绿色的内存段中,显然 p 不会影响到 str。

当 GetMemory() 函数执行完毕后,系统会回收它所用过的栈帧,这其实就是一般C语言编程教科书上常说的“局部变量在函数运行完毕后,就自动释放了”的原因。

好了,现在知道为什么 GetMemory() 函数没能把申请到的内存段返回给 str 了,那怎样修改才能让C语言程序按照初衷运行呢?要是 GetMemory() 函数能够修改 str 的值就好办了,这就需要将 str 的地址告诉 GetMemory() 函数。所以上面的面试题C语言程序做如下修改就可以正确运行了:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char* ptr;
void GetMemory(ptr* p, int num)
{
     *p = (ptr)malloc(sizeof(char)*num);
 }
int main()
{
    ptr str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf("str: %s\n", str);
    return 0;
}


现在编译C语言程序并运行,终于得到正常的结果了:

# gcc t.c 
# ./a.out 
str: hello

小结

你看,这道面试题其实注重的仍然只是基础。要是基础不牢,出现问题却找不到,甚至对其视而不见就麻烦了。

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