【问题标题】:Allocating struct with variable length array member分配具有可变长度数组成员的结构
【发布时间】:2011-11-30 07:11:08
【问题描述】:

我知道我可以使用new char[n] 创建一个n 字符数组。即使n 不是编译时间常数,这也有效。

但是假设我想要一个大小变量,后跟 n 个字符:

我的第一次尝试如下:

struct Test
{
  std::size_t size;
  char a[];
};

但是,new Test[n] 似乎没有达到我的预期,而是分配了nsizes。

我还发现sizeof(std::string)在ideone是4,看来它可以在一个块中同时分配大小和char数组。

有什么方法可以实现我所描述的(大概是std::string 已经做到的)?

【问题讨论】:

  • 如果您使用 VLA,这不是 C++。好的,我看到标题误导了我。
  • 我认为我们对这个问题的答案有一个连续的否决...
  • 向量呢?还是字符串成员?
  • 很容易创建自己的分配动态数组的类,但是只使用stringvector<char> 有什么问题?
  • "sizeof(std::string) 是 4" - wat

标签: c++ new-operator c++11 dynamic-memory-allocation


【解决方案1】:

虽然您可以这样做(它经常在 C 中用作某种变通方法),但不建议这样做。但是,如果这真的是您想要做的......这里有一种方法可以用于大多数编译器(包括那些不能很好地使用 C99 增强功能的编译器)。

#define TEST_SIZE(x) (sizeof(Test) + (sizeof(char) * ((x) - 1)))

typedef struct tagTest
{
    size_t size;
    char a[1];
} Test;

int elements = 10; // or however many elements you want
Test *myTest = (Test *)malloc(TEST_SIZE(elements));

C99 之前的 C 规范不允许结构中的零长度数组。为了解决这个问题,创建了一个包含单个元素的数组,并将比请求的元素计数少一个添加到实际结构的大小(大小和第一个元素)以创建预期的大小。

【讨论】:

  • 我可以看到这在这种情况下是如何工作的,但是如果将 size_t 更改为 short,并将 char 更改为 long,这不会成为问题,从而导致所有longs 没有正确对齐(或者没有分配足够的空间)?
  • 除非您使用编译器指令手动设置结构的打包,如果您将short 作为第一个成员并将数组定义为long,编译器将启动short在字节 0(到字节 1)处,跳过字节 2 到 7,然后在字节 8 处启动 long(保持正确对齐)。然后,在此之后分配/访问的每个 long 将按 8 字节对齐。
  • 这是有效的,因为sizeof(Test) 将解析为 8(根据我上面的评论),因为结构的大小将包括用于对齐的空字节。如果您尝试使用sizeof(short) + (sizeof(long) * x) 进行分配,您将不可避免地分配的空间太少。
  • 有没有办法用new 做到这一点,还是 malloc 是这里唯一的选择? (当然,我会将所有这些包装在构造函数中,所以使用 malloc 不是问题,只是出于兴趣)。
  • @Clinton:我认为这也可以使用placement new 来完成。
【解决方案2】:

你可以使用placement new:

#include <new>

struct Test {
    size_t size;
    char a[1];

    static Test* create(size_t size)
    {
        char* buf = new char[sizeof(Test) + size - 1];
        return ::new (buf) Test(size); 
    }

    Test(size_t s) : size(s)
    {
    }

    void destroy()
    {
        delete[] (char*)this;
    }
};

int main(int argc, char* argv[]) {
    Test* t = Test::create(23);
    // do whatever you want with t
    t->destroy();
}

【讨论】:

  • Miguel:当您使用new 分配一个char 数组时,当您使用它来存储Test 时,不会有潜在的对齐问题吗?无论如何要在new 上强制对齐?
  • 我认为不会有任何对齐问题。如果编译器在 struct 中插入填充,则 sizeof() 将报告它,因此填充将计入用 new 分配的内存中。我认为您最终可以使用此算法分配一些额外的字节,但绝不会更少。
  • @Clinton:new[] 为大小等于或小于请求大小的任何类型分配正确对齐的存储,所以这不是问题。但是,访问大于或等于其大小的数组索引是未定义的行为。随心所欲。
  • 如果有人试图访问t-&gt;a 的元素而不是t-&gt;a[0] 会导致未定义的行为
【解决方案3】:

您还可以使用“长度为 1 的数组”技巧。这是在 C 中:

struct Test {
    size_t size;
    char a[1];
}

int want_len = 2039;
struct Test *test = malloc(sizeof(struct Test) + (want_len - 1));
test->size = want_len;

GCC 也支持“0 长度”数组来达到这个目的: http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

【讨论】:

    【解决方案4】:

    如果我理解正确,您需要一个类来存储指向动态分配的以长度为前缀的字符串的单个指针。您可以利用 char* 可以安全地为任何内容设置别名这一事实来做到这一点。

    一个简单的实现,只是为了展示it can be done

    class LPS
    {
    private:
        char* ptr;
    
    public:
        LPS() noexcept : ptr(nullptr) {} // empty string without allocation
        explicit LPS(std::size_t len) {
            // Allocate everything in one go
            // new[] gives storage aligned for objects of the requested size or less
            ptr = new char[sizeof(std::size_t) + len];
            // Alias as size_t
            // This is fine because size_t and char have standard layout
            *reinterpret_cast<std::size_t*>(ptr) = len;
        }
        explicit LPS(char const* sz) {
            std::size_t len = std::char_traits<char>::length(sz);
            ptr = new char[sizeof(std::size_t) + len;
            *reinterpret_cast<std::size_t*>(ptr) = len;
            std::copy(sz, sz + len, ptr + sizeof(std::size_t));
        }
        LPS(LPS const& that) {
            if(that.ptr) {
                ptr = new char[sizeof(std::size_t) + that.size()];
                std::copy(that.ptr, that.ptr + sizeof(std::size_t) + that.size(), ptr);
            } else ptr = nullptr;
        }
        LPS(LPS&& that) noexcept {
            ptr = that.ptr;
            that.ptr = nullptr;
        }
        LPS& operator=(LPS that) {
            swap(that);
            return *this;
        }
        ~LPS() noexcept {
            // deleting a null pointer is harmless, no need to check
            delete ptr;
        }
        void swap(LPS& that) noexcept {
            std::swap(ptr, that.ptr);
        }
        std::size_t size() const noexcept {
             if(!ptr) return 0;
             return *reinterpret_cast<std::size_t const*>(ptr);
        }
        char* string() noexcept {
             if(!ptr) return nullptr;
             // the real string starts after the size prefix
             return ptr + sizeof(std::size_t);
        }
    };
    

    【讨论】:

    • 这并没有解决 OP 对希望在一个堆区域中分配整个对象的担忧(而不是在堆上分配一个带有指向堆上另一个对象的指针的对象,就像大多数字符串一样类。)
    • @AdamMaras 我认为确实如此。问题是“(...)它似乎可以在一个块中分配大小和字符数组。”,这正是它的作用。我会让 OP 决定。
    • @AdamMaras 不要动态分配对象,突然之间只有一层间接。像大多数字符串类一样:std::unique_ptr&lt;std::string&gt;(new std::string) 是谁?
    【解决方案5】:

    让我们在 C++ 中使用std::vector 让事情变得简短而甜蜜。

    struct Test
    {
       std::size_t size;
       char *a;  // Modified to pointer
    
       Test( int size ): size(size), a(new char[size+1])
       {}
    };
    
    std::vector<Test> objects(numberOfObjectsRequired,argumentToTheConstructor);
    

    【讨论】:

    • 这不是分配多个尺寸,而不仅仅是一个吗?
    • 好吧,有人正在疯狂投票。
    • sizeof(size) 总是一样的,所以我不认为a(new sizeof(size)) 是你的意思(即使你修正了语法)。
    • @ildjarn - 我明白你说的。我只是犯了一个如此简单的愚蠢错误。
    • 这违背了结合结构和数组分配的目的。
    猜你喜欢
    • 2020-01-15
    • 1970-01-01
    • 2015-08-09
    • 2011-04-19
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多