3

我被分配到一个项目,这是一个用 C++ 和 ActiveX 编写的复杂遗留系统 ~ 10 岁。

设置是 Microsoft Visual Studio 2008。

虽然目前系统没有问题,但作为遗留系统安全审查的一部分,由于安全漏洞,自动安全代码扫描工具已将 realloc 实例标记为不良实践问题。

这是因为 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 */
                }
            }

        }
    }
}

笔记:

  1. 在旧代码和新代码中都可以正确构造和销毁对象。

  2. 未检测到内存泄漏(由内置 CRT 调试堆函数的 Visual Studio 报告:http: //msdn.microsoft.com/en-us/library/e5ewb1h3 (v=vs.90).aspx )

  3. 我编写了一个小型测试工具(控制台应用程序),它执行以下操作:

    一个。添加 500000 个包含 2 个整数和一个 STL 字符串的类实例。

    添加的整数正在运行计数器,其字符串表示形式如下:

    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 行不同长度的大日志文件,添加到此数组的一个实例:CArray of CStrings 和 CArray of strings。

我使用现有方法(基线)和我修改的方法运行测试工具。我在调试和发布版本中都运行了它。

结果如下:

测试 1:调试构建 -> 添加具有 int、int、string、100000 个实例的类:

原始实现:5 秒,修改后的实现:12 秒

测试 2:调试构建 -> 添加具有 int、int、string、500000 个实例的类:

原始实现:71 秒,修改后的实现:332 秒

测试 3:发布版本 -> 添加具有 int、int、string、100000 个实例的类:

原始实现:2 秒,修改后的实现:7 秒

测试 4:发布版本 -> 添加具有 int、int、string、500000 个实例的类:

原始实现:54 秒,修改后的实现:183 秒

将大日志文件读入 CString 对象的 CArray:

测试 5:调试构建 -> 读取包含 86527 行 CArray 的 CString 的大日志文件

原始实现:5 秒,修改后的实现:5 秒

测试 6:发布版本 -> 读取包含 86527 行 CArray 的 CString 的大日志文件

原始实现:5 秒,修改后的实现:5 秒

将大日志文件读入字符串对象的 CArray:

测试 7:调试构建 -> 读取包含 86527 行字符串 CArray 的大日志文件

原始实现:12 秒,修改后的实现:16 秒

测试 8:发布版本 -> 读取包含 86527 行字符串 CArray 的大日志文件

原始实现:9 秒,修改后的实现:13 秒

问题:

  1. 从上面的测试可以看出,realloc 始终比 memalloc、memcpy 和 free 更快。在某些情况下(例如,Test-2)它的速度快了 367%。同样,对于 Test-4,它是 234%。那么我能做些什么来降低这些与 realloc 实现相当的数字呢?

  2. 我的版本可以提高效率吗?

假设:

  1. 请注意,我不能使用 C++ new 和 delete。我必须只使用 malloc 和 free。我也无法更改任何其他方法(因为它是现有功能)并且影响很大。所以我的双手被绑住,以尽可能地获得 realloc 的最佳实现。

  2. 我已经验证我修改后的实现在功能上是正确的。

PS:这是我的第一个 SO 帖子。我试图尽可能详细。对张贴的建议也表示赞赏。

4

3 回答 3

2

如果您查看 realloc 的实现,例如

http://www.scs.stanford.edu/histar/src/pkg/uclibc/libc/stdlib/malloc/realloc.c

您会看到您的实现与现有实现之间的区别在于它扩展了内存堆块,而不是使用低级调用创建一个全新的块。这可能是速度差异的部分原因。

我认为您还需要在每次执行 realloc 时考虑内存 memset 的影响,因为那样性能下降似乎是不可避免的。

我发现关于 realloc 将代码留在内存中的论点有点过于偏执,因为对于正常的 malloc/calloc/free 也可以这样说。这意味着您不仅需要找到所有 reallocs/malloc/callocs,还需要找到内部使用这些函数的任何运行时或 3rd 方函数,以确保没有任何内容保存在内存中,或者另一种方法是创建自己的堆并用普通的替换它以保持清洁。

于 2013-11-07T11:51:46.167 回答
2

首先我想指出你没有解决这个漏洞,因为 free 释放的内存也没有被清除,就像 realloc 一样。

另请注意,您的代码比旧的 realloc 做得更多:当内存不足时,它会引发异常。这可能是徒劳的。

为什么你的代码比 realloc 慢?可能是因为 realloc 正在使用您无法使用的幕后快捷方式。例如,realloc 分配的内存可能比您实际请求的更多,或者在前一个块结束后分配连续内存,因此您的代码执行的 memcpy 比 realloc 更多。

以防万一。在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;
}

你能做些什么来让你的代码更快?您可以尝试在当前分配的块之后分配更多内存,但随后释放内存变得更加成问题,因为您需要记住您分配的所有指针,或者找到一种方法来修改 free 使用的查找表以释放正确数量的记忆一次。

或者

使用通用策略(在内部)分配两倍于之前分配的内存,并(可选地)仅在新阈值小于分配内存的一半时才缩小内存。
这为您提供了一些空间,因此并非每次内存增长时都需要调用 malloc/memcpy/free。

于 2013-11-07T10:50:42.520 回答
1

从概念上讲,realloc() 并没有做任何太聪明的事情——它通过一些块分配内存,就像你在 ReAllocNew 中所做的那样。

唯一的概念差异可能在于新块大小的计算方式。

realloc 可能会使用这样的东西:

int new_buffer_size = old_buffer_size * 2;

这将减少您所拥有的内存移动次数。无论如何,我认为块大小计算公式是关键因素。

于 2013-11-07T02:34:45.590 回答