本系列文章主要分析国内外知名公司的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
小结
你看,这道面试题其实注重的仍然只是基础。要是基础不牢,出现问题却找不到,甚至对其视而不见就麻烦了。