我被分配到一个项目,这是一个用C ++和ActiveX编写的复杂的遗留系统,大约10年。
安装程序是Microsoft Visual Studio 2008.
虽然系统目前没有任何问题,但作为旧系统安全审核的一部分,由于安全漏洞,自动安全代码扫描工具已将重新分配的实例标记为“不良实践”问题。
这是因为realloc函数可能会将敏感信息的副本留在内存中,而不会被覆盖。该工具建议用malloc,memcpy和free替换realloc。
现在realloc函数是通用的,当源缓冲区为空时将分配内存。当缓冲区的大小为0时,它还释放内存。我能够验证这两种情况。 资料来源:MDSN图书馆2001
realloc返回一个指向重新分配(可能已移动)的内存块的void指针。如果大小为零且缓冲区参数不为NULL,或者如果没有足够的可用内存将块扩展为给定大小,则返回值为NULL。在第一种情况下,原始块被释放。在第二个中,原始块保持不变。返回值指向存储空间,该存储空间保证适当地对齐以存储任何类型的对象。要获取指向void之外的类型的指针,请在返回值上使用类型转换。
所以,我使用malloc,memcpy和free的替换函数必须满足这些情况。
我在原始代码片段(数组实现)下面再现了一个使用realloc动态调整大小并缩小其内部缓冲区的代码片段。
首先是类定义:
template <class ITEM>
class CArray
{
// Data members:
protected:
ITEM *pList;
int iAllocUnit;
int iAlloc;
int iCount;
public:
CArray() : iAllocUnit(30), iAlloc(0), iCount(0), pList(NULL)
{
}
virtual ~CArray()
{
Clear(); //Invokes SetCount(0) which destructs objects and then calls ReAlloc
}
现有的ReAlloc方法:
void ReAllocOld()
{
int iOldAlloc = iAlloc;
// work out new size
if (iCount == 0)
iAlloc = 0;
else
iAlloc = ((int)((float)iCount / (float)iAllocUnit) + 1) * iAllocUnit;
// reallocate
if (iOldAlloc != iAlloc)
{
pList = (ITEM *)realloc(pList, sizeof(ITEM) * iAlloc);
}
}
以下是我的实现,用malloc,memcpy和free替换它们:
void ReAllocNew()
{
int iOldAlloc = iAlloc;
// work out new size
if (iCount == 0)
iAlloc = 0;
else
iAlloc = ((int)((float)iCount / (float)iAllocUnit) + 1) * iAllocUnit;
// reallocate
if (iOldAlloc != iAlloc)
{
size_t iAllocSize = sizeof(ITEM) * iAlloc;
if(iAllocSize == 0)
{
free(pList); /* Free original buffer and return */
}
else
{
ITEM *tempList = (ITEM *) malloc(iAllocSize); /* Allocate temporary buffer */
if (tempList == NULL) /* Memory allocation failed, throw error */
{
free(pList);
ATLTRACE(_T("(CArray: Memory could not allocated. malloc failed.) "));
throw CAtlException(E_OUTOFMEMORY);
}
if(pList == NULL) /* This is the first request to allocate memory to pList */
{
pList = tempList; /* assign newly allocated buffer to pList and return */
}
else
{
size_t iOldAllocSize = sizeof(ITEM) * iOldAlloc; /* Allocation size before this request */
size_t iMemCpySize = min(iOldAllocSize, iAllocSize); /* Allocation size for current request */
if(iMemCpySize > 0)
{
/* MemCpy only upto the smaller of the sizes, since this could be request to shrink or grow */
/* If this is a request to grow, copying iAllocSize will result in an access violation */
/* If this is a request to shrink, copying iOldAllocSize will result in an access violation */
memcpy(tempList, pList, iMemCpySize); /* MemCpy returns tempList as return value, thus can be omitted */
free(pList); /* Free old buffer */
pList = tempList; /* Assign newly allocated buffer and return */
}
}
}
}
}
注意:
在旧代码和新代码中正确构造和销毁对象。
未检测到内存泄漏(由CRT调试堆函数中内置的Visual Studio报告:http://msdn.microsoft.com/en-us/library/e5ewb1h3(v=vs.90).aspx)
我写了一个小测试工具(控制台应用程序),它执行以下操作:
一个。添加包含2个整数和STL字符串的500000个实例。
添加的整数正在运行计数器及其字符串表示形式如下:
for(int i = 0; i < cItemsToAdd; i++)
{
ostringstream str;
str << "x=" << 1+i << "\ty=" << cItemsToAdd-i << endl;
TestArray value(1+i, cItemsToAdd-i, str.str());
array.Append(&value);
}
湾打开一个包含86526行不同长度的大日志文件,添加到此数组的实例:CStrings的CArray和字符串的CArray。
我使用现有方法(基线)和我修改过的方法运行测试工具。我在调试和发布版本中运行它。
Test-1:Debug build - &gt;使用int,int,string,100000实例添加类:
原始实施:5秒,修改后的实施:12秒
测试-2:调试构建 - &gt;使用int,int,string,500000实例添加类:
原始实施:71秒,修改后的实施:332秒
测试-3:发布版本 - &gt;使用int,int,string,100000实例添加类:
原始实施:2秒,修改后的实施:7秒
测试-4:发布版本 - &gt;使用int,int,string,500000实例添加类:
原始实现:54秒,修改后的实现:183秒
Test-5:Debug build - &gt;使用86527行CArray CString读取大日志文件
原始实施:5秒,修改后实施:5秒
测试-6:发布版本 - &gt;使用86527行CArray CString读取大日志文件
原始实施:5秒,修改后实施:5秒
Test-7:Debug build - &gt;用86527行CArray of string
读取大日志文件原始实施:12秒,修改后的实施:16秒
测试-8:发布版本 - &gt;用86527行CArray of string
读取大日志文件原始实施:9秒,修改后实施:13秒
从上面的测试中可以看出,与memalloc,memcpy和free相比,realloc始终更快。在某些情况下(例如,测试-2),它的速度快了367%。同样,对于Test-4,它是234%。那么如何才能将这些数字降低到与realloc实现相当的程度呢?
我的版本可以提高效率吗?
请注意我不能使用C ++ new和delete。我只能使用malloc和free。我也无法改变任何其他方法(因为它是现有功能)并且影响巨大。因此,我的双手必须得到最好的realloc实现。
我已经验证我的修改后的实现在功能上是正确的。
PS:这是我的第一篇SO帖子。我试图尽可能详细。关于发布的建议也很感激。
答案 0 :(得分:2)
首先,我想指出你没有解决这个漏洞,因为free释放的内存也没有被清除,就像realloc一样。
另请注意,您的代码不仅仅是旧的realloc:它会在内存不足时抛出异常。这可能是徒劳的。
为什么你的代码比realloc慢?可能是因为realloc正在使用引擎盖下的快捷方式,而这些快捷方式是您无法使用的。例如,realloc可能会分配比您实际请求更多的内存,或者在前一个块结束后分配连续内存,因此您的代码比realloc更加memcpy。
以防万一。在CompileOnline中运行以下代码会得到结果Wow no copy
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
void* p = realloc(NULL, 1024);
void* t = realloc(p, 2048);
if (p == t)
cout << "Wow no copy" << endl;
else
cout << "Alas, a copy" << endl;
return 0;
}
您可以做些什么来加快代码的速度? 您可以try to allocate more memory after the currently allocated block,但随后释放内存变得更加成问题,因为您需要记住您分配的所有指针,或者找到一种方法来修改free使用的查找表,以便一次性释放正确的内存量。
OR
使用(内部)分配两倍于先前分配的内存的公共策略,并且(可选)仅在新阈值小于分配的内存的一半时缩小内存。
这给你一些头部空间,所以不是每次内存增长都需要调用malloc / memcpy / free。
答案 1 :(得分:2)
如果你看一下realloc的实现,例如
http://www.scs.stanford.edu/histar/src/pkg/uclibc/libc/stdlib/malloc/realloc.c
您会看到您的实施与现有实施之间的差异 是它扩展了内存堆块而不是创建一个全新的块 通过使用低级别的电话。这可能是一些速度差异的原因。
我认为每次执行realloc时你还需要考虑memset of memory的含义,因为这样性能下降似乎是不可避免的。
我发现关于realloc在内存中留下代码的论点有些过于偏执,因为关于普通的malloc / calloc / free也是如此。这意味着您不仅需要查找所有reallocs / malloc / callocs,还需要内部使用这些函数的任何运行时或第三方函数,以确保内存中没有任何内容,或者另一种方法是创建自己的堆并用常规的替换它以保持清洁。
答案 2 :(得分:1)
从概念上讲,realloc()没有做任何太聪明的事情 - 它通过一些块分配内存,就像你在ReAllocNew中一样。
唯一的概念差异可能在于如何计算新的块大小。
realloc可能会使用以下内容:
int new_buffer_size = old_buffer_size * 2;
这将减少你在那里的内存移动次数。 无论如何,我认为块大小计算公式是关键因素。