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

内容均以php5.6.14为例.

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

[[email protected] www]$ php 2.php
[122,3333]
[Tue Jul 10 15:34:42 2016]  Script:  ‘/home/www/2.php‘
/home/weichen/Downloads/pdoner/pdoner.c(83) :  Freeing 0x7F86B52F79F8 (32 bytes), script=/home/www/2.php
[Tue Jul 10 15:34:42 2016]  Script:  ‘/home/www/2.php‘
/home/weichen/Downloads/php-5.6.14/ext/standard/string.c(1161) :  Freeing 0x7F86B52F7B60 (79 bytes), script=/home/www/2.php
=== Total 2 memory leaks detected ===

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

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

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

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

第(1)处:

[Tue Jul 10 15:34:42 2016]  Script:  ‘/home/www/2.php‘
/home/weichen/Downloads/pdoner/pdoner.c(83) :  Freeing 0x7F86B52F79F8 (32 bytes), script=/home/www/2.php

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

zval_ptr_dtor(&glue);

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

[[email protected] www]$ php 2.php
[Tue Jul 10 14:58:25 2016]  Script:  ‘/home/www/2.php‘
---------------------------------------
/home/weichen/Downloads/php-5.6.14/Zend/zend_execute.h(79) : Block 0x7fb3f93459c8 status:
/home/weichen/Downloads/php-5.6.14/Zend/zend_variables.c(37) : 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 (0)

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

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

ZVAL_NULL(glue);
zval_ptr_dtor(&glue);

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

第(2)处:

[Tue Jul 10 15:34:42 2016]  Script:  ‘/home/www/2.php‘
/home/weichen/Downloads/php-5.6.14/ext/standard/string.c(1161) :  Freeing 0x7F86B52F7B60 (79 bytes), script=/home/www/2.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, "成功", 1);
    add_index_string(msg, PDONER_ERRS_FAIL, "失败", 1);
    add_index_string(msg, PDONER_ERRS_EXCEP, "异常", 1);
    add_index_string(msg, PDONER_ERRS_UNKNOW, "未知", 1);

    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 10 17:04:38 2016] Script: ‘/home/www/2.php‘
---------------------------------------
/home/weichen/Downloads/php-5.6.14/Zend/zend_execute.h(79) : Block 0x7fbf5ed4bb4e status:
/home/weichen/Downloads/php-5.6.14/Zend/zend_variables.c(37) : 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 10 17:06:56 2016] Script: ‘/home/www/2.php‘
/home/weichen/Downloads/php-5.6.14/Zend/zend_API.c(1369) : Freeing 0x7F1900D44820 (72 bytes), script=/home/www/2.php
/home/weichen/Downloads/php-5.6.14/Zend/zend_hash.c(419) : Actual location (location was relayed)
Last leak repeated 3 times
[Thu Jul 10 17:06:56 2016] Script: ‘/home/www/2.php‘
/home/weichen/Downloads/php-5.6.14/Zend/zend_API.c(1366) : Freeing 0x7F1900D448C8 (32 bytes), script=/home/www/2.php
Last leak repeated 3 times
[Thu Jul 10 17:06:56 2016] Script: ‘/home/www/2.php‘
/home/weichen/Downloads/pdoner/pdoner.c(120) : Freeing 0x7F1900D45C58 (32 bytes), script=/home/www/2.php
[Thu Jul 10 17:06:56 2016] Script: ‘/home/www/2.php‘
/home/weichen/Downloads/php-5.6.14/Zend/zend_hash.c(392) : Freeing 0x7F1900D45D58 (64 bytes), script=/home/www/2.php
/home/weichen/Downloads/php-5.6.14/Zend/zend_alloc.c(2583) : Actual location (location was relayed)
[Thu Jul 10 17:06:56 2016] Script: ‘/home/www/2.php‘
/home/weichen/Downloads/pdoner/pdoner.c(121) : Freeing 0x7F1900D467C0 (72 bytes), script=/home/www/2.php
/home/weichen/Downloads/php-5.6.14/Zend/zend_API.c(1011) : Actual location (location was relayed)
=== Total 11 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(1024);

    strcat(dest, src1);
    strcat(dest, ori);
    strcat(dest, src2);

    RETURN_STRING(dest, 0);

显然 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, 0, 1); 

    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

@黑眼诗人 <www.farwish.com>

时间: 2024-10-14 20:55:54

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

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

http://www.cocoachina.com/ios/20160706/16951.html 本文授权转自MrPeak技术分享(公众号:MrPeakTech) 新款Objective-C内存泄漏自动检测工具PLeakSniffer,GitHub地址. 背景 前些天读到WeRead团队分享的一款内存泄漏检测工具MLeaksFinder,恍惚想起早些时候自己也有过编写这样一个小工具的想法,不知道由于什么原因把这事给忘记了.在仔细读过MLeaksFinder源码,了解实现思路之后,发现和自己最初

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

随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概述 像C语言这样的编程语言,具有简单的内存管理功能函数,例如malloc( )和free( ).开发人员可以使用这些功能函数来显式地分配和释放系统的内存. 当创建对象和字符串等时,JavaScript就会分配内存,并在不再使用时自动释放内存,这种机制被称为垃圾收集.这种释放资源看似是"自动"

SGI STL内存配置器存在内存泄漏吗?

阅读了SGI的源码后对STL很是膜拜,很高质量的源码,从中学到了很多.温故而知新!下文中所有STL如无特殊说明均指SGI版本实现. STL 内存配置器 STL对内存管理最核心部分我觉得是其将C++对象创建过程分解为构造.析构和内存分配.释放两类操作分离开来!摆脱了对频繁调用new或malloc函数想操作系统申请空间而造成的低效.其中析构操作时对具有non-trival.trival 析构函数的class区别对待也提高了效率.SGI 的两级配置器结构属于锦上添花. STL内存配置器有没有内存泄漏?

(转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对 C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃 C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能

JS内存泄漏 和Chrome 内存分析工具简介(摘)

原文地址:http://web.jobbole.com/88463/ JavaScript 中 4 种常见的内存泄露陷阱 原文:Sebastián Peyrott 译文:伯乐在线专栏作者 - ARIGATO 链接:http://web.jobbole.com/88463/ 点击 → 了解如何加入专栏作者 了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读

如何使用Valgrind memcheck工具进行C/C++的内存泄漏检测

系统编程中一个重要的方面就是有效地处理与内存相关的问题.你的工作越接近系统,你就需要面对越多的内存问题.有时这些问题非常琐碎,而更多时候它会演变成一个调试内存问题的恶梦.所以,在实践中会用到很多工具来调试内存问题. Valgrind是运行在Linux上一套基于仿真技术的程序调试和分析工具,它包含一个内核--一个软件合成的CPU,和一系列的小工具,每个工具都可以完成一项任务──调试,分析,或测试等.Valgrind可以检测内存泄漏和内存违例,还可以分析cache的使用等,灵活轻巧而又强大,能直穿程

Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector

那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简称vld). vld工具是VC++环境下一款小巧易用.免费开源的内存泄漏检测工具,vld可以显示导致内存泄漏的完整内存分配调用堆栈.vld的检测报告能够对每个内存泄漏点提供完整的堆栈跟踪,并且包含其源文件及行号信息. 安装过程是,先在到地址http://vld.codeplex.com/下载vld安

Java 理论与实践: 用弱引用堵住内存泄漏---转载

要让垃圾收集(GC)回收程序不再使用的对象,对象的逻辑 生命周期(应用程序使用它的时间)和对该对象拥有的引用的实际 生命周期必须是相同的.在大多数时候,好的软件工程技术保证这是自动实现的,不用我们对对象生命周期问题花费过多心思.但是偶尔我们会创建一个引用,它在内存中包含对象的时间比我们预期的要长得多,这种情况称为无意识的对象保留(unintentional object retention). 全局 Map 造成的内存泄漏 无意识对象保留最常见的原因是使用 Map 将元数据与临时对象(trans

Linux/Unix用valgrind检测内存泄漏

c\c++编程中,内存管理是比较头疼的问题,一不小心就会造成内存泄漏,除了养成良好的编程习惯外(使用智能指针等),使用工具也能帮助检测内存泄漏,valgrind是Unix\Linux下一款不错的工具.(mac下暂时有bug) Valgrind 安装 到www.valgrind.org下载最新版valgrind-3.2.3.tar.bz2 解压安装包:tar –jxvf valgrind-3.2.3.tar.bz2 解压后生成目录valgrind-3.2.3 cd valgrind-3.2.3 运