查看: 3631|回复: 4

C语言之内存使用

[复制链接]
发表于 2014-4-27 02:35:33 | 显示全部楼层 |阅读模式
关键词: C语言
这里我和大家一起探讨c语言的内存使用。
  曾经有同行写了一个把整数转换为字符串的函数:
char *itoa (int n)
{
  char retbuf[20];
  sprintf(retbuf, "%d", n);
  return retbuf;
}
  倘若我调用该函数:char *str5 = itoa(5),str5会是什么数值?
  结果是不确定,唯一能确定的是结果不会是我们想要的 “5”。
    为何呢?因为retbuf定义在函数体中,它是局部变量,局部变量的内存空间位于堆栈(stack)中,同时其作用范围也仅限于所在的函数中。此时当itoa()函数返回时,retbuf在堆栈中的内容将被回收,这块内存地址将可能被存放别的内容。所以把局部变量返回给函数调用者是欠妥的,也是不应该的做法。
  这样我们该如何解决问题呢,别担心,方法有很多且不止一个,下面就来阐述三种能解决这个问题的方法:
  1)、在itoa()函数内部用malloc() 为指针分配内存,同时将结果存放到里面,最后将retbuf返回给调用者。因为此时retbuf分配于堆(heap)中,其对应空间不会随着函数返回而释放,所以能达到我们的目的。
  不过这里需要注意:调用者在不需要retbuf的时候必须人工把它释放,调用free函数来回收空间,否则就造成内存泄漏了。倘若该函数和调用函数的都是同一个人所写则问题不大,否则将比较容易会疏漏此释放内存的动作。
  2)、在itoa()函数内部定义静态变量static char retbuf[20],这同样能保证函数返回后retbuf的空间不被回收,这是因为静态变量并不是存放在堆栈中,而是存放在一个叫“.bss”段的地方,该地方的内容是不会因函数返回而被回收的。
  这种办法虽然能解决问题,不过它也导致了函数变成了一个不可重入函数(即不能保证相同的输入肯定有相同的输出)。同时, retbuf [] 中的内容会被函数的下一次调用结果所代替,该办法不值得推荐。
  3)、使用指针参数,将函数定义为char *itoa(int n, char *retbuf),并且retbuf由函数调用者申请和释放,这时候itoa()只是将转换结果存放到retbuf。
  很明显这种方法比第一、二种方法强,首先避免了方法1对函数的影响,同时也规避了方法2对内存分配释放的影响,是业内一种比较通用流行的做法。
  扩展分析:
  如果就该问题本身而言,想必大家都可以迅速想到答案,问题关键就在对memory这类敏感资源的正确和合理地利用,下来我们对内存做个简单的分析:


  1)、程序中分为不同的内存段,包含:
  .heap - 堆,由程序显式分配和收回,如果不收回就是内存泄漏。
  .bss - 未初始化全局/静态变量,在整个软件执行过程中有效;
  .data - 已初始化全局/静态变量,在整个软件执行过程中有效;
  .stack - 函数调用栈,其中的内容在函数执行期间有效,并由编译器负责分配和收回;
  2)、自己管理的内存尽量自己申请和释放。
  这其实是一个内存分配和释放的基本原则,比方说上面的第二种方法,由itoa()分配的内存,却由调用者释放,就不是一个十分好的做法,它明显不如第三种,由调用者自己申请和释放。此外该原则还有另一层意思:若使用一个指针,最好先确保它已经指向一个合法地址,否则就自己分配,不然即非法地址访问。许多程序的致命错误都是访问一个没有指向合法内存区的指针,也就是野指针,也包括空指针。


问题:内存分配 & sizeof
  如果使用sizeof来计算一个指针变量,希望得到这个指针变量所分配的内存块的大小,可以吗?


char *p = NULL;
int nMemSize = 0;

p = malloc(1024);
nMemSize = sizeof(p);


  答案与分析:


  结果是达不到你的要求的,sizeof只能告诉你指针变量本身占用的内存大小。指针所指向的内存,如果是malloc分配的,sizeof 是无法知道的。换言之,malloc分配的内存是无法向内存管理模块进行事后查询的,当然你是可以自己编写代码来管理维护。


  问题:栈内存使用


  下面的程序运行会有什么问题?


char *GetString(void)
{
  char p[] = "hello world";
  return p;// 编译器将提出警告
}


void Test4(void)
{
  char *str = NULL;
  str = GetString();// str 的内容是垃圾
  cout<< str << endl;
}


  答案与分析:


  返回栈内存,内存可能被销毁也可能不被及时销毁,但可以肯定的是出了作用域之后已被标记成可被系统使用,因此会返回乱七八糟不可知内容。当然,返回的指针的内容,应该是不变的,特殊时候是有用的,比如,可以用来探测系统内存分配规律等。


  问题:内存使用相关编程规范


  如果想尽可能地避免内存使用上的问题,有什么捷径吗?


  答案与分析:


  除非做一件从没有人做过的事情,不然都是有捷径可言的,答案那就是站在前人的肩膀上,当今各个大公司都有自己的编码规范,这些规范凝聚了许多的经验教训,有较高的使用价值,考虑到这些规范在网上流传很多,这里我就不再列举了,感兴趣的,推荐参考林锐的《高质量C/C++编程指南》。


发表于 2014-4-28 17:45:04 | 显示全部楼层
是想阐述函数调用过程么?
 楼主| 发表于 2014-4-30 02:39:26 | 显示全部楼层
云峰 发表于 2014-4-28 17:45
是想阐述函数调用过程么?

不是,这里是将内存的使用,比如堆栈和堆的区别。
发表于 2014-5-4 09:00:59 | 显示全部楼层
复制下来!!!!!!!!!我自己好好学习!!!!!!!!!!!!!!
谢谢.jpg
发表于 2014-5-4 20:46:30 | 显示全部楼层
Hugo801122 发表于 2014-4-30 02:39
不是,这里是将内存的使用,比如堆栈和堆的区别。

但函数的调用过程不就是堆栈的使用过程么
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们  -  服务条款  -  使用指南  -  站点地图  -  友情链接  -  联系我们
电子工程网 © 版权所有   京ICP备16069177号 | 京公网安备11010502021702
快速回复 返回顶部 返回列表