今天在教学妹C语言的时候,重新写了一道题目,在没有任何准备的情况下,毫不意外地翻车了。
翻车的点是关于函数返回char *类型时,并不能够如期得到期望的结果。
发现问题
题目:
写一个函数fun,实现将整型数int转化成字符串char * 。要求使用函数递归。
顺带一提,本文暂时认为这个整数是大于等于1 的,并且长度不固定。
功能上并没有复杂性,但是因为一定要使用递归,就一定涉及char * 返回值类型的函数。
先说一下一开始的思路:
一位一位地转换。然后拼凑起来变成一个完整的字符串。
如:fun(1234) -> fun(123) +“4” -> fun(12) + “34” ->fun(1) +“234” -> “1234” -> return
如果fun其中的加号可以理解成strcat,这要求前后两个都是字符串。
明确了这种思路以后,代码很容易就可以写好:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* num_to_ascii(int n) {
// 1 -> "1\0"; 0 -> "0->"0\0"
// assert n<= 9
char c[2];
c[0] = n+48;
c[1] = '\0';
return &c[0];
}
char* fun(int num) {
if (num <= 9) {
return num_to_ascii(num);
}
char* s1 = fun(num / 10);
char* s2 = num_to_ascii(num % 10);
return strcat(s1,s2);
}
int main() {
int num = 789;
printf("%s", fun(num));
return 0;
}
解释一下,这里面因为想用string.h中的库函数strcat,所以特意把num_to_ascii函数的返回类型从char变成了char*,说实话,这非常不符合我代码方面的审美。但是自己写一个char *和char的合并又显得有些愚蠢,折衷后写成了现在的样子。
现在编译运行一下:
结果错误了,而且莫名其妙。重新编译运行一下:
同一段代码,用一个编译器编译运行后,出现不同的结果。
分析问题
毫无疑问,一定是哪里出了问题,代码似乎没有什么随机元素,为什么输出结果会不一样呢?。
但是bug为什么会产生呢?经过仔细地排查,原来是num_to_ascii函数有问题。
可以写一个愚蠢的小代码测试一下:
#include<stdio.h>
char* fun1() {
char c[2];
c[0] = '5';
c[1] = '\0';
return &c[0];
}
int main() {
printf("%s", fun1());
return 0;
}
输出结果会是‘5’嘛?很显然不是。
为什么会出现这样的结果呢?
先看看下面这个小幅度修改的代码吧:
#include<stdio.h>
#include<stdlib.h>
char* fun1() {
char* c = (char*)malloc(2 * sizeof(char));
c[0] = '5';
c[1] = '\0';
return c;
}
int main() {
char* p = NULL;
printf("%s", p = fun1());
free(p);
return 0;
}
这个代码为什么又是能够输出正确结果呢?
当函数返回作为数组名的c之后,临时字符数组的空间就会被释放。之后又重新调用了新的函数printf,导致最终根据返回指针所输出的结果变成了刷新后的值,而这个值几乎可以认为是随机的。
更加专业地说,num_to_ascii中的常量c是建立在栈上的,在函数结束后就会被释放。如果我们希望保留其中的字符串,应该把它的变量建立在堆上,从而实现手动管理变量寿命。
解决问题
实际上,这种问题可以通过静态变量申明或者malloc来解决。
我们使用index记录当前循环的次数。然后把静态数组对应的位置用相同的赋值转换。
解决过后,实际代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char num_to_ascii(int n) {
// 1 -> "1"; 0 -> "0->"0"
// assert input n <= 9
char c= n+ '0';
return c;
}
char* fun(int num) {
static char s[100];
static int index = 0;
if (num / 10 != 0) {
fun(num / 10);
}
s[index++] = num_to_ascii(num%10);
s[index] = '\0';
return s;
}
int main() {
int num = 123456;
printf("%s", fun(num));
return 0;
}
经过测试时可以正确运行的。
通过这种写法,也回避掉了因为想使用strcat函数而不得不把num_to_ascii函数的尴尬。
至此,问题已经全部解决了。
结论
堆和栈,这块一定要弄清楚!大厂面试最喜欢追着这两点问了!
掌握得越熟练,调试这种bug就越快!