VC调用giflib(4):内存泄漏与功能缺失
作者:马健
邮箱:stronghorse_mj@hotmail.com
主页:http://www.comicer.com/stronghorse
发布:2020.03.14
一、EGifSpew的内存泄漏与功能缺失
在上一篇笔记里说过,我因为要计算全局调色板和局部调色板,所以只能用EGifSpew对GIF进行编码,但giflib中对这个函数的实现存在严重的内存泄漏问题。
1)EGifPutScreenDesc函数调用造成的内存泄漏
在EGifSpew函数开头部分是这样调用EGifPutScreenDesc函数的:
int
EGifSpew(GifFileType *GifFileOut)
{
int i, j;
if (EGifPutScreenDesc(GifFileOut,
GifFileOut->SWidth,
GifFileOut->SHeight,
GifFileOut->SColorResolution,
GifFileOut->SBackGroundColor,
GifFileOut->SColorMap) == GIF_ERROR) {
return (GIF_ERROR);
}
……
而在EGifPutScreenDesc函数中,是这样操作的:
int
EGifPutScreenDesc(GifFileType *GifFile,
const int Width,
const int Height,
const int ColorRes,
const int BackGround,
const ColorMapObject *ColorMap)
{
GifByteType Buf[3];
GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private;
const char *write_version;
GifFile->SColorMap = NULL;
……
if (ColorMap) {
GifFile->SColorMap = GifMakeMapObject(ColorMap->ColorCount,
ColorMap->Colors);
……
} else
GifFile->SColorMap = NULL;
即一进EGifPutScreenDesc函数,三不管就把GifFile->SColorMap赋空了,后面再重新给它分配内存。而在我的源代码中,是先给GifFile->SColorMap分配了全局调色板的,结果进去后就成了野指针。这个操作够不够骚?说实话我刚看到的时候真的是被雷到了,不由感慨我还是太年轻,这世上还有无数的奇葩我还没见识过。
解决办法就是在调用EGifPutScreenDesc函数前先对GifFile->SColorMap指针进行备份,调用后释放掉即可。
2)EGifCloseFile函数调用造成的内存泄漏
一般使用EGifSpew函数生成动画GIF的过程,都是先把各帧图像存储到GifFileType结构体的SavedImages数组中,然后再调用EGifSpew把数组中的各帧顺序编码、存盘。但是EGifSpew的问题就在于各帧编码完成后,并没有释放SavedImages数组中的各帧内存,直接就调用EGifCloseFile函数释放掉GifFileType结构体,结果SavedImages数组中的各帧就成了没爹没娘的野指针。
解决的办法就是在调用EGifCloseFile之前,先调用GifFreeSavedImages释放掉SavedImages数组中分配的内存。
除了上述内存泄漏问题外,EGifSpew函数中还存在一个功能缺失:在EGifPutScreenDesc后,没有写入动画GIF的循环次数,导致生成的GIF文件只能动一次。正常情况下循环次数是写在帧数据前面的,所以应该在EGifPutScreenDesc之后,在for循环写各帧数据之前。
最后我实在是没办法,只能把原版EGifSpew函数复制一份出来修改成MyEGifSpew,完整代码如下:
// MyEGifSpew要用到此函数,但在egif_lib.c中此函数是static局部函数,所以照抄一遍
static int
EGifWriteExtensions(GifFileType *GifFileOut,
ExtensionBlock *ExtensionBlocks,
int ExtensionBlockCount)
{
if (ExtensionBlocks) {
int j;
for (j = 0; j < ExtensionBlockCount; j++) {
ExtensionBlock *ep = &ExtensionBlocks[j];
if (ep->Function != CONTINUE_EXT_FUNC_CODE)
if (EGifPutExtensionLeader(GifFileOut, ep->Function) == GIF_ERROR)
return (GIF_ERROR);
if (EGifPutExtensionBlock(GifFileOut, ep->ByteCount, ep->Bytes) == GIF_ERROR)
return (GIF_ERROR);
if (j == ExtensionBlockCount - 1 || (ep+1)->Function != CONTINUE_EXT_FUNC_CODE)
if (EGifPutExtensionTrailer(GifFileOut) == GIF_ERROR)
return (GIF_ERROR);
}
}
return (GIF_OK);
}
// 原版的EGifSpew存在内存漏洞,而且没有设置循环播放次数,导致生成的GIF只能播放一次
// nLoopTimes:循环播放次数,0表示无限循环播放
static int MyEGifSpew(GifFileType *GifFileOut, int nLoopTimes)
{
int i, j;
// EGifPutScreenDesc会破坏GifFileOut->SColorMap,所以调用前必须先备份,否则出现野指针
ColorMapObject* SColorMap = GifFileOut->SColorMap;
if (EGifPutScreenDesc(GifFileOut,
GifFileOut->SWidth,
GifFileOut->SHeight,
GifFileOut->SColorResolution,
GifFileOut->SBackGroundColor,
GifFileOut->SColorMap) == GIF_ERROR) {
return (GIF_ERROR);
}
// 在EGifPutScreenDesc中重新分配了GifFileOut->SColorMap,所以手工释放前面保存的
GifFreeMapObject(SColorMap);
// 在共享调色板之后,写入循环次数。原版EGifSpew少了这一项,所以生成的GIF只能动一次
{
/* Create a Netscape 2.0 loop block */
unsigned char params[3] = {1, 0, 0}; // 后两个字节是循环次数,0表示无限循环
params[1] = (nLoopTimes & 0xff);
params[2] = (nLoopTimes >> 8) & 0xff;
if (EGifPutExtensionLeader(GifFileOut, APPLICATION_EXT_FUNC_CODE) != GIF_OK ||
EGifPutExtensionBlock(GifFileOut, 11, "NETSCAPE2.0") != GIF_OK ||
EGifPutExtensionBlock(GifFileOut, 3, params) != GIF_OK ||
EGifPutExtensionTrailer(GifFileOut) != GIF_OK
)
return (GIF_ERROR);
}
for (i = 0; i < GifFileOut->ImageCount; i++) {
SavedImage *sp = &GifFileOut->SavedImages[i];
int SavedHeight = sp->ImageDesc.Height;
int SavedWidth = sp->ImageDesc.Width;
/* this allows us to delete images by nuking their rasters */
if (sp->RasterBits == NULL)
continue;
if (EGifWriteExtensions(GifFileOut,
sp->ExtensionBlocks,
sp->ExtensionBlockCount) == GIF_ERROR)
return (GIF_ERROR);
if (EGifPutImageDesc(GifFileOut,
sp->ImageDesc.Left,
sp->ImageDesc.Top,
SavedWidth,
SavedHeight,
sp->ImageDesc.Interlace,
sp->ImageDesc.ColorMap) == GIF_ERROR)
return (GIF_ERROR);
if (sp->ImageDesc.Interlace) {
/*
* The way an interlaced image should be written -
* offsets and jumps...
*/
int InterlacedOffset[] = { 0, 4, 2, 1 };
int InterlacedJumps[] = { 8, 8, 4, 2 };
int k;
/* Need to perform 4 passes on the images: */
for (k = 0; k < 4; k++)
for (j = InterlacedOffset[k];
j < SavedHeight;
j += InterlacedJumps[k]) {
if (EGifPutLine(GifFileOut,
sp->RasterBits + j * SavedWidth,
SavedWidth) == GIF_ERROR)
return (GIF_ERROR);
}
} else {
for (j = 0; j < SavedHeight; j++) {
if (EGifPutLine(GifFileOut,
sp->RasterBits + j * SavedWidth,
SavedWidth) == GIF_ERROR)
return (GIF_ERROR);
}
}
}
if (EGifWriteExtensions(GifFileOut,
GifFileOut->ExtensionBlocks,
GifFileOut->ExtensionBlockCount) == GIF_ERROR)
return (GIF_ERROR);
// 原版EGifSpew少了这一句,导致大量的内存漏洞
GifFreeSavedImages(GifFileOut);
if (EGifCloseFile(GifFileOut, NULL) == GIF_ERROR)
return (GIF_ERROR);
return (GIF_OK);
}
上面的代码修正了两处内存泄漏,并允许调用时设置循环次数。在写入循环次数的时候,使用了最流行的NETSCAPE2.0,没有用比较少见的ANIMEXTS1.0。
另外如果是对细节比较在意的程序员,在调用EGifSpew开始写GIF文件之前,还应该调用EGifGetGifVersion函数,让giflib自动设置输出的GIF文件的版本。虽然不调用也不会有啥大影响,但细节终归是细节。
二、EGifPutImageDesc函数中的内存泄露
这个内存泄漏比较隐蔽。先看该函数中的一段代码:
if (ColorMap != GifFile->Image.ColorMap) {
if (ColorMap) {
// 用ColorMap更新GifFile->Image.ColorMap
……
} else {
GifFile->Image.ColorMap = NULL;
}
}
如果满足第一层条件ColorMap != GifFile->Image.ColorMap,说明这两个指针总有一个非空,第二层判断的else块中ColorMap为空情况下,GifFile->Image.ColorMap就必然不为空,这个时候把它直接赋空,妥妥的内存泄露。
解决方法很简单,在
GifFile->Image.ColorMap = NULL;
之前加一句
GifFreeMapObject(GifFile->Image.ColorMap);
即可。
我喜欢VC的原因之一,就是VC有内存泄漏报告机制,即在debug状态下应用退出后,会逐条报告所有未被释放的内存及该条内存申请时的编号,有了编号再找导致内存泄漏的源代码就相对比较容易,下条件断点即可。
(全文完)

浙公网安备 33010602011771号