从getmemery()函数看内存管理、函数传参等一系列问题

在C 面试题目中,会经常出现getmemery()函数的改错题,比如下面这道题,

例一:代码如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. char *getmemery()  
  4. {  
  5.     char p[] = "hello world!";  
  6.     return p;  
  7. }  
  8.   
  9. main()  
  10. {  
  11.     char *str = NULL;  
  12.     str = getmemery();  
  13.     printf("%s\n",str);  
  14. }  
这题主要考察的是我们对 内存管理 的了解;

咱们先执行一下,先不管编译时会出现什么错误,执行结果如下:

可以看到执行结果是一段乱码,而不是想象中的 hello world!

为什么会出现这种结果,在编译是就能看到,编译时出现了警告如下:


警告:函数返回局部变量的地址;函数返回局部变量的地址会产生什么后果呢

我们知道,局部变量存储在栈区,在代码块执行前申请一片内存,执行完毕后,这块内存即被释放;*getmemery()函数是个指针型函数,指针型函数返回的是一个指针,就是返回的是一个地址,但是指针型函数要注意的是,其返回的地址必须是函数调用结束后依然存在的存储单位地址;而此处p[]是局部变量,其返回的地址p在函数调用结束后已经不存在了,所以执行是会出现乱码!先看看如何更改会正确,代码如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. char *getmemery()  
  4. {  
  5.     char *p = "hello world!";  
  6.     return p;  
  7. }  
  8.   
  9. main()  
  10. {  
  11.     char *str = NULL;  
  12.     str = getmemery();  
  13.     printf("%s\n",str);  
  14. }  

执行结果如下:


执行结果正确!

看看代码,只是将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为指针常量,不能修改,当然也不能赋值!


回到刚才的两段代码,结果的差别便区别在上述论述中!

当然,我们也可以这样改:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2.   
  3. char *getmemery()  
  4. {  
  5.     static char p[] = "hello world!";  
  6.     return p;  
  7. }  
  8.   
  9. main()  
  10. {  
  11.     char *str = NULL;  
  12.     str = getmemery();  
  13.     printf("%s\n",str);  
  14. }  
结果如下:


仍能得到正确结果!

static的作用在这里先不详解,但C语言面试中,经常会考察static的作用,static的作用简单说就两种:(1)限制变量的作用域;(2)限制变量的生存周期;

所以上述代码中用static 修饰p[],使p[]此时不是存储在栈区,而是存储在静态存储区,生存周期是整个程序的开始到结束!


例二:下面再给出一个getmemery()函数的改错题,代码如下:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. void getmemery(char *p)  
  6. {  
  7.     p = (char *)malloc(100);  
  8. }  
  9.   
  10. main()  
  11. {  
  12.     char *str = NULL;  
  13.     getmemery(str);  
  14.     strcpy(str,"hello world!");  
  15.     printf("%s\n",str);  
  16. }  
这题考察的是 我们对函数传参 的理解!

我们先对代码进行编译,并没有错误与警告,执行结果如下:


段错误 (核心已转存储),这个错误在前面的文章中提到过,现在再解释一下;

一 般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指 向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的 表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了 越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了.  在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的

   1)访问系统数据区,尤其是往 系统保护的内存地址写数据    最常见就是给一个指针以0地址 

   2)内存越界(数组越界,变量类型不一致等)

   3) 访问到不属于你的内存区域  

这段文字是复制别人的,我们先来解决问题,从上述描述中,问题还是出在错误的使用指针:
(1)访问系统数据区,尤其是往 系统保护的内存地址写数据    最常见就是给一个指针以0地址 
这里并不是这个原因
(2)内存越界(数组越界,变量类型不一致等)
这里我们给其分配的大小是足够的
(3) 访问到不属于你的内存区域  
问题出在这,上述代码传入getmemery(char *p)函数的字符串指针是形参,在函数内部修改形参并不能真正的改变传入形参的值,执行完char *str = NULL; gememory(str);后的str仍为NULL;
一般函数的传递都是值传递,不会改变函数外的变量值。简单地说,就是形参不能够改变实参,实参只是复制了一份给形参!其自身并没有被改变


所以str所指向的仍是一个未知区域,所以会出此上述错误;

如何修改呢?

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. void getmemery(char **p)  
  6. {  
  7.     *p = (char *)malloc(100);  
  8. }  
  9.   
  10. main()  
  11. {  
  12.     char *str = NULL;  
  13.     getmemery(&str);  
  14.     strcpy(*str,"hello world!");  
  15.     printf("%s\n",*str);  
  16. }  

执行结果如下:

[cpp]  view plain  copy
  1. fs@ubuntu:~/qiang/tmp$ gcc -o 1 1.c  
  2. fs@ubuntu:~/qiang/tmp$ ./1  
  3. hello world!  
  4. fs@ubuntu:~/qiang/tmp$   
这就是我们常说的 “地址传递”, 将str的地址传给getmemery()函数, getmemery()函数就会通过地址修改str里面的值,这样就会得到正确的结果。

所以,我们要记住函数传参的两种方式:1)值传递 2)地址传递

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值