【问题标题】:Allocate chunk of memory for array of structs为结构数组分配内存块
【发布时间】:2010-07-26 08:31:54
【问题描述】:

我需要在一块可靠的内存中分配一个此结构的数组。 “char *extension”和“char *type”的长度在编译时是未知的。

struct MIMETYPE
{
 char *extension;
 char *type;
};

如果我使用“new”操作符来单独初始化每个元素,内存可能会分散。这就是我尝试为其分配单个连续内存块的方式:

//numTypes = total elements of array
//maxExtension and maxType are the needed lengths for the (char*) in the struct
//std::string ext, type;
unsigned int size = (maxExtension+1 + maxType+1) * numTypes;
mimeTypes = (MIMETYPE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);

但是,当我尝试像这样加载数据时,当我稍后尝试访问它时,数据全部乱序和分散。

for(unsigned int i = 0; i < numTypes; i++)
{
 //get data from file
 getline(fin, line);
 stringstream parser.str(line);
 parser >> ext >> type;

 //point the pointers at a spot in the memory that I allocated
 mimeTypes[i].extension = (char*)(&mimeTypes[i]);
 mimeTypes[i].type = (char*)((&mimeTypes[i]) + maxExtension);

 //copy the data into the elements
 strcpy(mimeTypes[i].extension, ext.c_str());
 strcpy(mimeTypes[i].type, type.c_str());
}

谁能帮帮我?

编辑:

unsigned int size = (maxExtension+1 + maxType+1);
mimeTypes = (MIMETYPE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size * numTypes);

for(unsigned int i = 0; i < numTypes; i++)
    strcpy((char*)(mimeTypes + (i*size)), ext.c_str());
    strcpy((char*)(mimeTypes + (i*size) + (maxExtension+1)), type.c_str());

【问题讨论】:

  • 有什么理由不能只使用std::vectorstd::string
  • @cppnick:它是new[] 的安全包装器。我想我不明白的是:为什么它 all 必须是连续的? 可能 不是进行过早的代码混乱优化的好理由。首先让它工作,使用好的、现代的、干净的 C++,然后 profile 它,看看什么是慢的(不是什么可能是慢的),然后优化它。
  • 最好的方法是在没有连续内存要求的情况下编写它。然后放置一整套单元测试来验证行为。然后,如果您确实需要性能改进,请重构您选择的分配模型,使用测试作为安全网来捕获(更复杂的)代码中的任何错误。
  • 为什么你认为连续分配会提高性能?除非您正在进行线性搜索(如果性能很重要,您不应该这样做),那么您将以随机顺序访问字符串,并且位置可能不会有任何区别。
  • 决定只使用 std::string。

标签: c++


【解决方案1】:

你混合2分配:

1) 管理 MIMETYPE 数组和

2) 管理字符数组

可能是(我不太了解您的目标):

struct MIMETYPE
{
    char extension[const_ofmaxExtension];
    char type[maxType];
};

最好以表格形式分配线性项:

new MIMETYPE[numTypes];

【讨论】:

  • +1 来自我(尽管这只是引出了std::string 有什么问题的问题)。
  • const_ofmaxExtension 在编译时未知,并试图节省内存。
  • @cppnick - 这只是我的符号,你必须在那里使用常量表达式(不是变量)。对于 c++,声明 **const unsigned maxExtension = ... ** 就足够了
【解决方案2】:

我将搁置这是过早的优化(你应该只使用 std::string、std::vector 等),因为其他人已经说过了。

我看到的根本问题是您对 MIMETYPE 结构和它们将指向的字符串使用相同的内存。无论你如何分配它,指针本身和它所指向的数据都不能在内存中占据确切相同的位置。


假设您需要一个包含 3 种类型的数组,并让 MIMETYPE* mimeTypes 指向您为它们分配的内存。

这意味着您将这段记忆视为包含:

8 bytes: mime type 0
8 bytes: mime type 1
8 bytes: mime type 2

现在,考虑一下您在下一行代码中所做的事情:

mimeTypes[i].extension = (char*)(&mimeTypes[i]);

extension 被设置为指向与 MIMETYPE 结构本身相同的内存位置。那是行不通的。当后续代码写入extension 指向的位置时,它会覆盖 MIMETYPE 结构。

同样,这段代码:

strcpy((char*)(mimeTypes + (i*size)), ext.c_str());

正在将字符串数据写入您希望 MIMETYPE 结构占用的同一内存中。


如果您真的想将所有必要的内存存储在一个连续的空间中,那么这样做会有点复杂。您需要分配一块内存以在其开头包含 MIMETYPE 数组,然后是字符串数据。

例如,假设您需要 3 种类型。假设扩展字符串 (maxExtension) 的最大长度为 3,类型字符串 (maxType) 的最大长度为 10。在这种情况下,您的内存块需要布置为:

8 bytes: mime type 0
8 bytes: mime type 1
8 bytes: mime type 2
4 bytes: extension string 0
11 bytes: type string 0
4 bytes: extension string 1
11 bytes: type string 1
4 bytes: extension string 2
11 bytes: type string 2

因此,要正确分配、设置和填充所有内容,您需要执行以下操作:

unsigned int mimeTypeStringsSize = (maxExtension+1 + maxType+1);
unsigned int totalSize = (sizeof(MIMETYPE) + mimeTypeStringsSize) * numTypes;
char* data = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalSize);

MIMETYPE* mimeTypes = (MIMETYPE*)data;
char* stringData = data + (sizeof(MIMETYPE) * numTypes);

for(unsigned int i = 0; i < numTypes; i++)
{
    //get data from file
    getline(fin, line);
    stringstream parser.str(line);
    parser >> ext >> type;

    // set pointers to proper locations
    mimeTypes[i].extension = stringData + (mimeTypeStringsSize * i);
    mimeTypes[i].type = stringData + (mimeTypeStringsSize * i) + maxExtension+1;

    //copy the data into the elements
    strcpy(mimeTypes[i].extension, ext.c_str());
    strcpy(mimeTypes[i].type, type.c_str());
}

(注意:我的字节布局解释基于 32 位代码的典型行为。64 位代码将有更多空间用于指针,但原理是相同的。此外,我的实际代码不管 32/64 位差异如何,这里写的应该都能工作。)

【讨论】:

  • 是的!谢谢!出于某种原因,指针会占用内存的事实让我忘记了。我还没有尝试过,但我的错误现在似乎很明显了。
【解决方案3】:

您需要做的是获取垃圾收集器并管理堆。使用 RAII 进行对象销毁的简单收集器编写起来并不难。这样,您可以简单地分配收集器并知道它将是连续的。但是,在确定这对您来说是一个严重的问题之前,您应该真正地、真正地进行分析。发生这种情况时,您可以 typedef 许多 std 类型(例如 string 和 stringstream)来使用您的自定义分配器,这意味着您可以返回到 std::string 而不是那里的 C 风格字符串恐怖。

【讨论】:

  • 今天刚买了SC2...哈哈@邪恶火山星球“Char”
【解决方案4】:

您确实必须知道extensiontype 的长度才能连续分配MIMETYPE(如果“连续”意味着extensiontype 实际上是在对象内分配的)。由于您说extensiontype 的长度在编译时是未知的,因此您不能在数组或vector 中执行此操作(vector 的总长度可以在运行时设置和更改,但是在编译时必须知道各个元素的大小,如果不知道extensiontype 的长度,就无法知道该大小。

我个人建议使用MIMETYPEs 中的vector,并将extensiontype 字段都设为strings。您的要求听起来很可疑,就像过早的优化一样,直觉是解引用指针很慢,尤其是在指针导致缓存未命中的情况下。除非您有实际数据表明读取这些字段是一个实际瓶颈,否则我不会担心这一点。

但是,我可以想到一个可能的“解决方案”:当MIMETYPE 对象内的extensiontype 字符串短于特定阈值时,您可以分配它们,否则动态分配它们:

#include <algorithm>
#include <cstring>
#include <new>

template<size_t Threshold> class Kinda_contig_string {
    char contiguous_buffer[Threshold];
    char* value;

    public:

    Kinda_contig_string() : value(NULL) { }

    Kinda_contig_string(const char* s)
    {
         size_t length = std::strlen(s);
         if (s < Threshold) {
             value = contiguous_buffer;
         }
         else {
             value = new char[length];
         }

         std::strcpy(value, s);
    }

    void set(const char* s)
    {
        size_t length = std::strlen(s);

        if (length < Threshold && value == contiguous_buffer) {
            // simple case, both old and new string fit in contiguous_buffer
            // and value points to contiguous_buffer
            std::strcpy(contiguous_buffer, s);
            return;
        }

        if (length >= Threshold && value == contiguous_buffer) {
            // old string fit in contiguous_buffer, new string does not
            value = new char[length];
            std::strcpy(value, s);
            return;
        }

        if (length < Threshold && value != contiguous_buffer) {
            // old string did not fit in contiguous_buffer, but new string does
            std::strcpy(contiguous_buffer, s);
            delete[] value;
            value = contiguous_buffer;
            return;
        }

        // old and new strings both too long to fit in extension_buffer
        // provide strong exception guarantee
        char* temp_buffer = new char[length];
        std::strcpy(temp_buffer, s);
        std::swap(temp_buffer, value);
        delete[] temp_buffer;
        return;
    }

    const char* get() const
    {
        return value;
    }
}

class MIMETYPE {
    Kinda_contig_string<16> extension;
    Kinda_contig_string<64> type;

  public:
    const char* get_extension() const
    {
        return extension.get();
    }

    const char* get_type() const
    {
        return type.get();
    }

    void set_extension(const char* e)
    {
        extension.set(e);
    }

    // t must be NULL terminated
    void set_type(const char* t)
    {
        type.set(t);
    }

    MIMETYPE() : extension(), type() { }

    MIMETYPE(const char* e, const char* t) : extension(e), type(t) { }
};

我真的无法在不感到内疚的情况下支持这一点。

【讨论】:

  • 实际上你已经实现的被称为小字符串优化,它很可能已经在你的标准库的std::string类中实现 -并且保证没有错误。您正在复制已经实现、测试并证明有用的代码。 确实,在进行此类优化之前,您首先需要衡量这是否有必要。 Visage 的评论是对这个问题的最佳答案:首先编写代码,使其干净且有效,以及测试。然后分析,然后优化,使用测试确保没有任何问题。
  • 我修复了实现中的一些错误。我想指出,我在写原始答案时同意你,这就是我写整个第二段的原因。这也是我写最后一段的原因。
  • “我修复了实现中的一些错误。”真的,这就是你不应该这样做的原因。除非你是 Donald Knuth,否则这些东西往往不会开箱即用。使用经过测试和验证的实现会更好。
  • “除非你是 Donald Knuth,否则这些东西往往永远不会开箱即用。”那很好笑。我刚读完“文学编程”,我最喜欢的两章是“TeX 的错误”(对类和错误原因的一般讨论)和“TeX 错误日志”(原始错误日志)。
【解决方案5】:

在字符串之间添加一个字节...扩展名和类型不是 \0 终止的方式。

在这里你分配允许额外的 \0 - OK

unsigned int size = (maxExtension+1 + maxType+1) * numTypes;
mimeTypes = (MIMETYPE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);

这里你没有为扩展的结尾留下任何空间 \0(如果字符串 len == maxExtension)

 //point the pointers at a spot in the memory that I allocated
 mimeTypes[i].extension = (char*)(&mimeTypes[i]);
 mimeTypes[i].type = (char*)((&mimeTypes[i]) + maxExtension);

我认为应该是

 mimeTypes[i].type = (char*)((&mimeTypes[i]) + maxExtension + 1);

【讨论】:

  • 你是对的,而且,我不应该使用 mimeTypes[i] 作为偏移量,因为它只有 2 个字节......我添加了我必须在上面使用的代码,但我是尝试使用它时遇到访问冲突
猜你喜欢
  • 2014-04-21
  • 2020-02-03
  • 1970-01-01
  • 2021-05-29
  • 2018-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多