[php-src]Php扩展的内存泄漏处理思路

时间:2022-12-13 18:34:40

内容均以php5.6.14为例.

一. 封装函数时产生 memory leaks.

[weichen@localhost www]$ php .php
[,]
[Tue Jul :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/pdoner/pdoner.c() : Freeing 0x7F86B52F79F8 ( bytes), script=/home/www/.php
[Tue Jul :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/php-5.6./ext/standard/string.c() : Freeing 0x7F86B52F7B60 ( bytes), script=/home/www/.php
=== Total memory leaks detected ===

php编译开启 --enable-debug,如果扩展中存在内存泄漏,会有相应提示。内存泄漏问题相当困扰。

为什么会有内存泄露?是你的函数一直在申请内存做某件事,而功能完成后没有释放内存。

网上的 hello world 程序很多,基本是不讲内存处理的,即便稍作修改,也无法用于真实项目。

所以释放内存也是底层程序的关键点。现在分析一下上面的提示信息,总共检测有 2 处内存泄漏。

第(1)处:

[Tue Jul  :: ]  Script:  '/home/www/2.php'
/home/weichen/Downloads/pdoner/pdoner.c() : Freeing 0x7F86B52F79F8 ( bytes), script=/home/www/.php

提示我们 pdoner.c(83) 有问题,回到程序中是 MAKE_STD_ZVAL(glue); 给 glue 初始化没问题,问题是用完了没有释放,很容易想到的是要释放掉 glue。

zval_ptr_dtor(&glue);

编译安装依旧有问题,出现段错误一般是指针使用有误:

[weichen@localhost www]$ php .php
[Tue Jul :: ] Script: '/home/www/2.php'
---------------------------------------
/home/weichen/Downloads/php-5.6./Zend/zend_execute.h() : Block 0x7fb3f93459c8 status:
/home/weichen/Downloads/php-5.6./Zend/zend_variables.c() : Actual location (location was relayed)
Invalid pointer: ((thread_id=0x007A7C7A) != (expected=0x06567840))
Segmentation fault (core dumped)

zval_ptr_dtor 使用有什么讲究?这时候最好先查阅一下内核中的用法。

zend_API.h 中有这样一处用法:

#define ZVAL_ZVAL(z, zv, copy, dtor) do {       \
zval *__z = (z); \
zval *__zv = (zv); \
ZVAL_COPY_VALUE(__z, __zv); \
if (copy) { \
zval_copy_ctor(__z); \
} \
if (dtor) { \
if (!copy) { \
ZVAL_NULL(__zv); \
} \
zval_ptr_dtor(&__zv); \
} \
} while ()

注意,在调用 zval_ptr_dtor 销毁 __zv 之前,调用了 ZVAL_NULL(__zv) 把指针置为null。

所以我们照这种方式,把 glue 设为null。

ZVAL_NULL(glue);
zval_ptr_dtor(&glue);

编译运行,可以看到只剩一处提示了。

第(2)处:

[Tue Jul  :: ]  Script:  '/home/www/2.php'
/home/weichen/Downloads/php-5.6./ext/standard/string.c() : Freeing 0x7F86B52F7B60 ( bytes), script=/home/www/.php

当时这个还没有解决掉,先继续往下看,回头再一起看这个的解决办法。

二. 封装类时产生 memory leaks.

PDONER_ERRS_* 均为定义的常量,下面是无内存泄漏的版本:

/* {{{ proto public Errs::__construct(void) */
PHP_METHOD(errs, __construct)
{
zval *msg;
MAKE_STD_ZVAL(msg);
array_init(msg); add_index_string(msg, PDONER_ERRS_SUCC, "成功", );
add_index_string(msg, PDONER_ERRS_FAIL, "失败", );
add_index_string(msg, PDONER_ERRS_EXCEP, "异常", );
add_index_string(msg, PDONER_ERRS_UNKNOW, "未知", ); zend_update_property(errs_ce, getThis(), ZEND_STRL(PDONER_ERRS_PROPERTY_NAME_MSG), msg TSRMLS_CC); zval_ptr_dtor(&msg); /*
add_index_string(msg, PDONER_ERRS_SUCC, "成功", 0);
add_index_string(msg, PDONER_ERRS_FAIL, "失败", 0);
add_index_string(msg, PDONER_ERRS_EXCEP, "异常", 0);
add_index_string(msg, PDONER_ERRS_UNKNOW, "未知", 0); add_property_zval_ex(getThis(), PDONER_ERRS_PROPERTY_NAME_MSG, sizeof(PDONER_ERRS_PROPERTY_NAME_MSG), msg TSRMLS_CC);
*/
}
/* }}} */

扩展类的属性无法直接初始化成数组和对象,所以只能以修改属性的方式操作,在构造函数内 或者 MINIT 阶段。

注意,由于属性赋值在 construct 阶段,如果没有实例化类,扩展内部 zend_read_property 时还是没有值的。

zend_update_property 第二个参数是当前对象,所以没有办法用在 MINIT 阶段,上面用法参考了 Yaf-2.3.5:

PHP_METHOD(yaf_config_ini, __construct) {
zval *filename, *section = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|z", &filename, &section) == FAILURE) {
zval *prop;
MAKE_STD_ZVAL(prop);
array_init(prop);
zend_update_property(yaf_config_ini_ce, getThis(), ZEND_STRL(YAF_CONFIG_PROPERT_NAME), prop TSRMLS_CC);
zval_ptr_dtor(&prop);
return;
} (void)yaf_config_ini_instance(getThis(), filename, section TSRMLS_CC);
}

非理想情况下:

1). 我们的构造函数中,如果传了非 duplicate 的参数:add_index_string(msg, PDONER_ERRS_SUCC, "成功", 0);

调用 Errs::$msg 可以成功输出内容,但是有段错误:

[Thu Jul  :: ] Script: '/home/www/2.php'
---------------------------------------
/home/weichen/Downloads/php-5.6./Zend/zend_execute.h() : Block 0x7fbf5ed4bb4e status:
/home/weichen/Downloads/php-5.6./Zend/zend_variables.c() : Actual location (location was relayed)
Invalid pointer: ((thread_id=0x646F6C70) != (expected=0x6BF6E840))
Segmentation fault (core dumped)

Reference:when to duplicate string using add_index_string?

http://grokbase.com/t/php/php-dev/01285y74sp/when-to-duplicate-string-using-add-index-string

有其它地方用,则复制一份出去;没有其它地方用,duplicate 填 0 .

2). 启用注释段代码的情况,可以成功输出,却有 11 处内存泄漏:

[Thu Jul  :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/php-5.6./Zend/zend_API.c() : Freeing 0x7F1900D44820 ( bytes), script=/home/www/.php
/home/weichen/Downloads/php-5.6./Zend/zend_hash.c() : Actual location (location was relayed)
Last leak repeated times
[Thu Jul :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/php-5.6./Zend/zend_API.c() : Freeing 0x7F1900D448C8 ( bytes), script=/home/www/.php
Last leak repeated times
[Thu Jul :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/pdoner/pdoner.c() : Freeing 0x7F1900D45C58 ( bytes), script=/home/www/.php
[Thu Jul :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/php-5.6./Zend/zend_hash.c() : Freeing 0x7F1900D45D58 ( bytes), script=/home/www/.php
/home/weichen/Downloads/php-5.6./Zend/zend_alloc.c() : Actual location (location was relayed)
[Thu Jul :: ] Script: '/home/www/2.php'
/home/weichen/Downloads/pdoner/pdoner.c() : Freeing 0x7F1900D467C0 ( bytes), script=/home/www/.php
/home/weichen/Downloads/php-5.6./Zend/zend_API.c() : Actual location (location was relayed)
=== Total memory leaks detected ===

可见是没有销毁 zval *msg 的缘故。你加上  zval_ptr_dtor(&msg); 又会报段错误。

注意上面的用法没有这样用:add_property_zval_ex(getThis(), ZEND_STRL(PDONER_ERRS_PROPERTY_NAME_MSG), msg TSRMLS_CC);

三. 字符串返回值的难题.

回到第(2)处的字符串问题上,刚开始是这么做的:

    char *src1 = "[";
char *ori = Z_STRVAL_P(return_value);
char *src2 = "]"; char *dest = (char *)emalloc(); strcat(dest, src1);
strcat(dest, ori);
strcat(dest, src2); RETURN_STRING(dest, );

显然 dest 是长期占用内存的,但你如何在返回值之后,还能再把它销毁呢,恐怕无法做到。

这里就要引入一个概念,当你的函数没有返回值时,函数默认返回的变量是 zval *return_value,也就是你用它就不会有问题。

另外,我们用内核中提供的字符串连接函数 concat_function(zval *result, zval *op1, zval *op2) 代替 strcat 更有效的处理。

concat_function 在 ./Zend/zend_operators.c:1422,有时候不清楚用法最好是看它的实现。

使用 concat_function 之后,有一些要注意的问题,看代码:

    // 第一种方法,引入一个变量
zval result; concat_function(&result, &op1, return_value TSRMLS_CC);
concat_function(&result, &result, &op2 TSRMLS_CC); // 1. zval_ptr_dtor(&return_value) was wrong.
// 2. forget zval_dtor(return_value) will cause memory leaks. zval_dtor(return_value); // copy result to return_value;
// if "zval result" is not zero-terminated, use ZVAL_ZVAL() instead, like the way 2. (PHP Warning: String is not zero-terminated.)
ZVAL_COPY_VALUE(return_value, &result); // 第二种方法,更简洁
concat_function(&op1, &op1, return_value TSRMLS_CC);
concat_function(&op1, &op1, &op2 TSRMLS_CC);
zval_dtor(return_value);
ZVAL_ZVAL(return_value, &op1, , ); zval_dtor(&op1);
zval_dtor(&op2);

上面是 pdoner 扩展函数 pd_implode_json 的实现:https://github.com/farwish/pdoner

php 未开启 debug 模式情况下使用 valgrind 工具:http://tina.reeze.cn/book/?p=chapt06/06-07-memory-leaks

Link: http://www.cnblogs.com/farwish/p/5663993.html

[php-src]Php扩展的内存泄漏处理思路的更多相关文章

  1. iOS内存泄漏自动检测工具PLeakSniffer

    新款objective-C内存泄漏自动检测工具 PLeakSniffer , GitHub地址 (https://github.com/music4kid/PLeakSniffer). 背景 前些天读 ...

  2. Netty堆外内存泄漏排查,这一篇全讲清楚了

    上篇文章介绍了Netty内存模型原理,由于Netty在使用不当会导致堆外内存泄漏,网上关于这方面的资料比较少,所以写下这篇文章,专门介绍排查Netty堆外内存相关的知识点,诊断工具,以及排查思路提供参 ...

  3. 调不尽的内存泄漏,用不完的Valgrind

    调不尽的内存泄漏,用不完的Valgrind Valgrind 安装 1. 到www.valgrind.org下载最新版valgrind-X.X.X.tar.bz2 2. 解压安装包:tar –jxvf ...

  4. JavaScript中的内存泄漏以及如何处理

    随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概 ...

  5. 【进阶1-5期】JavaScript深入之4类常见内存泄漏及如何避免(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://mp.weixin.qq.com/s/RZ8Lpkyk8lz6z5H8Q8SiEQ 垃圾回收算法 常用垃圾回收算法叫做**标记清除 ...

  6. JavaScript如何工作:垃圾回收机制 + 常见的4种内存泄漏

    原文地址: How JavaScript works: memory management + how to handle 4 common memory leaks 本文永久链接:https://d ...

  7. js晋级篇——前端内存泄漏探讨

    1.IE7/8 DOM对象或者ActiveX对象循环引用导致内存泄漏 循环引用分为两种: 第一种:多个对象循环引用 var a=new Object; var b=new Object; a.r=b; ...

  8. 系统剖析Android中的内存泄漏

    [转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...

  9. 了解 JavaScript 应用程序中的内存泄漏

    简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许多功能无需考虑内存管理 ...

随机推荐

  1. ThinkphpCMF笔记

    1.模板js,css文件__PUBLIC__ <link href="__TMPL__Public/style.css" rel="stylesheet" ...

  2. CentOS 6&period;5系统安装配置LAMP&lpar;Apache&plus;PHP5&plus;MySQL&rpar;服务器环境

    安装篇: 一.安装Apache yum install httpd #根据提示,输入Y安装即可成功安装 /etc/init.d/httpd start#启动Apache 备注:Apache启动之后会提 ...

  3. 如何快速掌握一款新的MCU? &lpar;转&rpar;

      发布时间:2013-12-15 10:27:51 技术类别:单片机     个人分类:话题思考       任何一款MCU,其基本原理和功能都是大同小异,所不同的只是其外围功能模块的配置及数量.指 ...

  4. sass揭秘之变量&lpar;转载&rpar;

    出处:http://www.w3cplus.com/preprocessor/sass-basic-variable.html 因为文章内含有很多sass代码,如需自己动手查看编译结果,推荐使用sas ...

  5. 面向对象编程&lpar;OOP&rpar;基础之UML基础

    在我们学习OOP过程中,难免会见到一些结构图~各种小框框.各种箭头.今天小猪就来简单介绍一下这些框框箭头的意思——UML. UML定义的关系主要有:泛化(继承).实现.依赖.关联.聚合.组合,这六种关 ...

  6. 正确率、召回率和 F 值

    原文:http://peghoty.blog.163.com/blog/static/49346409201302595935709/ 正确率.召回率和 F 值是在鱼龙混杂的环境中,选出目标的重要评价 ...

  7. 《分布式系统原理介绍》【PDF】下载

    内容简介 分布式系统理论体系非常庞大,涉及知识面也非常广博,本文精心选择了部分在工程实践中应用广泛.简单有效的分布式理论.算法.协议加以介绍.全文分为两大部分,第一部分介绍了分布式系统的一些基本概念并 ...

  8. 报错:Failed to execute goal org&period;apache&period;maven&period;plugins&colon;maven-compiler-plugin&colon;3&period;1

    错误现象: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-com ...

  9. Android NDK&colon; Application targets deprecated ABI&lpar;s&rpar;&colon; armeabi Open File

    Error:(81) Android NDK: Application targets deprecated ABI(s): armeabi Error:(82) Android NDK: Suppo ...

  10. web前端----html基础

    一.初始html 1.web服务本质 import socket sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.bind((&q ...