• 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状态下应用退出后,会逐条报告所有未被释放的内存及该条内存申请时的编号,有了编号再找导致内存泄漏的源代码就相对比较容易,下条件断点即可。

    (全文完)

  • 相关阅读:
    Using temporary与Using filesort
    mysql order by 造成语句 执行计划中Using filesort,Using temporary相关语句的优化解决
    mysql Using join buffer (Block Nested Loop) join连接查询优化
    Centos桥接静态IP配置方法
    shell脚本 tomcat自动备份发布服务
    Centos7离线安装Redis
    MySql存储引擎介绍,如何选择合适的引擎
    场效应管
    单片机指针作为函数形式参数
    指针数组与数组指针
  • 原文地址:https://www.cnblogs.com/stronghorse/p/16002888.html
Copyright © 2020-2023  润新知