You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PHP扩展C代码中调用gzinflate?自实现遇内存耗尽

在PHP扩展C代码中调用gzinflate的最佳方式及内存耗尽问题解决

问题概述

在PHP扩展的C代码中,不确定调用PHP内置gzinflate函数的最佳方式,目前自行基于zlib实现了类似功能(复用PHP zlib扩展代码),但遇到内存耗尽错误:

Fatal error: Allowed memory size of 134217728 bytes exhausted

当前实现代码如下:

头文件

#define PHP_ZLIB_ENCODING_RAW -0xf
#define PHP_ZLIB_ENCODING_GZIP 0x1f
#define PHP_ZLIB_ENCODING_DEFLATE 0x0f

#define PHP_ZLIB_ENCODING_ANY 0x2f

#define PHP_ZLIB_BUFFER_SIZE_GUESS(in_len) (((size_t) ((double) in_len * (double) 1.015)) + 10 + 8 + 4 + 1)

C文件实现

#include "zlib.h"
static voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size)
{
    return (voidpf)safe_emalloc(items, size, 0);
}

static void php_zlib_free(voidpf opaque, voidpf address)
{
    efree((void*)address);
}

static zend_string *php_zlib_encode(const char *in_buf, size_t in_len, int encoding, int level)
{
     int status;
     z_stream Z;
     zend_string *out;

     memset(&Z, 0, sizeof(z_stream));
     Z.zalloc = php_zlib_alloc;
     Z.zfree = php_zlib_free;

     if (Z_OK == (status = deflateInit2(&Z, level, Z_DEFLATED, encoding, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY))) {
          out = zend_string_alloc(PHP_ZLIB_BUFFER_SIZE_GUESS(in_len), 0);

          Z.next_in = (Bytef *) in_buf;
          Z.next_out = (Bytef *) ZSTR_VAL(out);
          Z.avail_in = in_len;
          Z.avail_out = ZSTR_LEN(out);

          status = deflate(&Z, Z_FINISH);
          deflateEnd(&Z);

          if (Z_STREAM_END == status) {
               /* size buffer down to actual length */
               out = zend_string_truncate(out, Z.total_out, 0);
               ZSTR_VAL(out)[ZSTR_LEN(out)] = '\0';
               return out;
          } else {
               zend_string_efree(out);
          }
     }

     php_error_docref(NULL, E_WARNING, "%s", zError(status));
     return NULL;
}

函数调用代码

zval data;
char *v;

v = "{\"ok\":1}";

php_json_decode_ex(&data, v, sizeof(v) - 1, 1, 512);

ZSTR_VAL(php_zlib_encode(Z_STRVAL_P(&data), Z_STRLEN_P(&data), PHP_ZLIB_ENCODING_DEFLATE, -1))

解决方案

方案1:直接调用PHP内置的gzinflate函数(推荐)

无需自行实现压缩逻辑,直接复用PHP内置的gzinflate函数,其自动处理内存管理、缓冲区调整等细节,避免手动实现的潜在bug。

示例代码:

zval data;
zval result;
char *v = "{\"ok\":1}";

// 修复JSON解码的长度错误:用strlen(v)替代sizeof(v)-1
php_json_decode_ex(&data, v, strlen(v), 1, 512);

// 获取gzinflate函数并调用
zend_string *func_name = zend_string_init("gzinflate", strlen("gzinflate"), 0);
zend_function *func;
if (zend_hash_find(CG(function_table), func_name, (void**)&func) == SUCCESS) {
    zval params[1];
    ZVAL_COPY(&params[0], &data);
    
    // 调用函数
    zend_call_function(&func, NULL, &result, 1, params);
    zend_string_release(func_name);

    // 处理返回值
    if (Z_TYPE(result) == IS_STRING) {
        char *inflated_data = Z_STRVAL(result);
        size_t inflated_len = Z_STRLEN(result);
        // 此处使用解压后的数据
    }
    
    // 释放返回值的引用
    zval_ptr_dtor(&result);
} else {
    php_error_docref(NULL, E_WARNING, "gzinflate function not found");
}

// 释放data的引用
zval_ptr_dtor(&data);

方案2:修复自行实现的内存问题

如果必须手动实现,需解决两个核心问题:

  1. 初始缓冲区大小猜测不足,导致压缩过程中内存溢出
  2. 调用代码中sizeof(v)-1的错误(指针的sizeof不等于字符串实际长度)

修改后的压缩函数:

static zend_string *php_zlib_encode(const char *in_buf, size_t in_len, int encoding, int level)
{
    int status;
    z_stream Z;
    zend_string *out = NULL;
    size_t out_len = PHP_ZLIB_BUFFER_SIZE_GUESS(in_len);
    int retries = 0;

    memset(&Z, 0, sizeof(z_stream));
    Z.zalloc = php_zlib_alloc;
    Z.zfree = php_zlib_free;
    Z.opaque = NULL;

    if (Z_OK != (status = deflateInit2(&Z, level, Z_DEFLATED, encoding, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY))) {
        php_error_docref(NULL, E_WARNING, "%s", zError(status));
        return NULL;
    }

    Z.next_in = (Bytef *)in_buf;
    Z.avail_in = in_len;

    do {
        // 分配或动态扩容缓冲区
        if (!out) {
            out = zend_string_alloc(out_len, 0);
        } else {
            out_len *= 2; // 每次扩容一倍
            out = zend_string_realloc(out, out_len, 0);
        }
        if (!out) {
            deflateEnd(&Z);
            php_error_docref(NULL, E_ERROR, "Failed to allocate memory for compression");
            return NULL;
        }

        Z.next_out = (Bytef *)ZSTR_VAL(out);
        Z.avail_out = out_len - Z.total_out;

        status = deflate(&Z, Z_FINISH);

        // 最多重试5次,避免无限扩容
    } while ((status == Z_OK || status == Z_BUF_ERROR) && retries++ < 5);

    deflateEnd(&Z);

    if (status == Z_STREAM_END) {
        out = zend_string_truncate(out, Z.total_out, 0);
        ZSTR_VAL(out)[ZSTR_LEN(out)] = '\0';
        return out;
    } else {
        if (out) {
            zend_string_efree(out);
        }
        php_error_docref(NULL, E_WARNING, "%s", zError(status));
        return NULL;
    }
}

修复后的调用代码:

zval data;
char *v = "{\"ok\":1}";

// 修复长度错误
php_json_decode_ex(&data, v, strlen(v), 1, 512);

zend_string *compressed = php_zlib_encode(Z_STRVAL_P(&data), Z_STRLEN_P(&data), PHP_ZLIB_ENCODING_DEFLATE, -1);
if (compressed) {
    char *compressed_data = ZSTR_VAL(compressed);
    // 使用压缩数据
    zend_string_efree(compressed);
}
zval_ptr_dtor(&data);

内容的提问来源于stack exchange,提问作者Mark Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 08:24:52