如何在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(¶ms[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:修复自行实现的内存问题
如果必须手动实现,需解决两个核心问题:
- 初始缓冲区大小猜测不足,导致压缩过程中内存溢出
- 调用代码中
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
相关产品推荐
相关产品推荐

