共计 3079 个字符,预计需要花费 8 分钟才能阅读完成。
c 语言 -getmemery()笔试题
从 getmemery()函数看内存管理、函数传参等一系列问题。
在 C 面试题目中,会经常出现 getmemery()函数的改错题,比如下面这道题。
示例一:
#include<stdio.h>
char*getmemery()
{char p[]="helloworld!";
return p;
}
main()
{
char*str=NULL;
str=getmemery();
printf("%s\n",str);
}
这题主要考察的是我们对 内存管理 的了解;
咱们先执行一下,先不管编译时会出现什么错误,执行结果如下:
jonathan@cloud:~/test$ ./a.out
@ͱ¿
可以看到执行结果是一段乱码,而不是想象中的 hello world!
为什么会出现这种结果,在编译是就能看到,编译时出现了警告如下:
jonathan@cloud:~/test$ gcc getmemary.c
getmemary.c: In function‘getmemery’:
getmemary.c:6:2: warning: function returns address of local variable [-Wreturn-local-addr]
return p;
^
警告:函数返回局部变量的地址;函数返回局部变量的地址会产生什么后果呢
我们知道,局部变量存储在栈区,在代码块执行前申请一片内存,执行完毕后,这块内存即被释放;*getmemery()函数是个指针型函数,指针型函数返回的是一个指针,就是返回的是一个地址,但是指针型函数要注意的是,其返回的地址必须是函数调用结束后依然存在的存储单位地址;而此处 p[]是局部变量,其返回的地址 p 在函数调用结束后已经不存在了,所以执行是会出现乱码!
先看看如何更改会正确,代码如下:
#include <stdio.h>
char *getmemery()
{
char *p = "hello world!";
return p;
}
main()
{
char *str = NULL;
str = getmemery();
printf("%s\n",str);
}
执行结果如下:
jonathan@cloud:~/test$ ./a.out
hello world!
执行结果正确!
看看代码,只是将 p[] = “hello world!” 改成了 *p = “hello world!”,结果却不同呢!将字符串赋给数组和指针有什么区别呢?
一个字符串,如 ”hello world!”, 一般为字符串常量,既然是常量, 存储在常量区,常量的生存周期是伴随着整个程序的,可以用它对字符指针赋值,或初始化,相当于把这个字符串常量的首地址赋给这个指针,正如上面代码中 char *p=”hello world!”;
但是,当用 ”hello world!” 给字符数组作初始化时,这里的 ”hello world!”, 并非一个字符串常量,只是复制了一份放在数组里,而是相当于一个初始化列表{‘h’,’e’,’l’,’l’,’o’,’ ‘,’w’,’o’,’r’,’l’,’d’,’0′},在其他任何时候,他对表示一个字符串常量。而数组名也是一个指针常量,不能对常量赋值。所以 char p[]=”hello world!” 正确,而 char p[12]; p=”hello world!” 错误,p 为指针常量,不能修改,当然也不能赋值!
回到刚才的两段代码,结果的差别便区别在上述论述中!
当然,我们也可以这样改:
#include <stdio.h>
char *getmemery()
{static char p[] = "hello world!";
return p;
}
main()
{
char *str = NULL;
str = getmemery();
printf("%s\n",str);
}
结果如下:
jonathan@cloud:~/test$ ./a.out
hello world!
仍能得到正确结果!
static 的作用在这里先不详解,但 C 语言面试中,经常会考察 static 的作用,static 的作用简单说就两种:(1)限制变量的作用域;(2)限制变量的生存周期;
所以上述代码中用 static 修饰 p[],使 p[]此时不是存储在栈区,而是 存储在静态存储区,生存周期是整个程序的开始到结束!
示例二:
下面再给出一个 getmemery()函数的改错题,代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void getmemery(char *p)
{p = (char *)malloc(100);
}
main()
{
char *str = NULL;
getmemery(str);
strcpy(str,"hello world!");
printf("%s\n",str);
}
这题考察的是 我们对函数传参 的理解!
我们先对代码进行编译,并没有错误与警告,执行结果如下:
jonathan@cloud:~/test$ ./a.out
Segmentation fault (core dumped)
段错误(核心已转存储),这个错误在前面的文章中提到过,现在再解释一下;
一 般来说, 段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由 gdtr 来保存的,他是一个 48 位的寄存器,其中的 32 位是保存由它指 向的 gdt 表,后 13 位保存相应于 gdt 的下标,最后 3 位包括了程序是否在内存中以及程序的在 cpu 中的运行级别, 指向的 gdt 是由以 64 位为一个单位的 表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了 越界访问,cpu 就会产生相应的异常保护,于是 segmentation fault 就出现了. 在编程中以下几类做法容易导致段错误, 基本是是错误地使用指针引起的
这段文字是复制别人的,我们先来解决问题,从上述描述中,问题还是出在错误的使用指针:
- 访问系统数据区,尤其是往 系统保护的内存地址写数据 最常见就是给一个指针以 0 地址 这里并不是这个原因
- 内存越界(数组越界,变量类型不一致等)这里我们给其分配的大小是足够的
- 访问到不属于你的内存区域
问题出在这,上述代码传入 getmemery(char p)函数的字符串指针是形参,在函数内部修改形参并不能真正的改变传入形参的值,执行完 char str = NULL; gememory(str); 后的 str 仍为 NULL;
一般函数的传递都是值传递,不会改变函数外的变量值。简单地说,就是形参不能够改变实参,实参只是复制了一份给形参!其自身并没有被改变
所以 str 所指向的仍是一个未知区域,所以会出此上述错误;
如何修改呢?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void getmemery(char **p)
{*p = (char *)malloc(100);
}
main()
{
char *str = NULL;
getmemery(&str);
strcpy(str,"hello world!");
printf("%s\n",str);
}
执行结果如下:
jonathan@cloud:~/test$ gcc getmemary.c
jonathan@cloud:~/test$ ./a.out
hello world!
这就是我们常说的“地址传递”,将 str 的地址传给 getmemery()函数,getmemery()函数就会通过地址修改 str 里面的值,这样就会得到正确的结果。
所以,我们要记住函数传参的两种方式:1)值传递 2)地址传递。