Allocating struct with variable length array membe

2019-01-12 04:07发布

I know I can do new char[n] to create an array of n chars. This works even when n is not a compile time constant.

But lets say I wanted a size variable followed by n chars:

My first attempt at this is the following:

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

However it seems new Test[n] doesn't do what I expect, and instead allocates n sizes.

I've also found that sizeof(std::string) is 4 at ideone, so it seems it can allocate both the size and the char array in one block.

Is there a way I can achieve what I described (presumably what std::string already does)?

5条回答
何必那么认真
2楼-- · 2019-01-12 04:24

You can use 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();
}
查看更多
贼婆χ
3楼-- · 2019-01-12 04:26

If I understand correctly, you want a class that stores a single pointer to a dynamically allocated length-prefixed string. You can do that by taking advantage of the fact that char* can safely alias anything.

A simplistic implementation, just to show how 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);
    }
};
查看更多
该账号已被封号
4楼-- · 2019-01-12 04:31

While you can do this (and it was often used in C as a workaround of sorts) it's not recommended to do so. However, if that's really what you want to do... here's a way to do it with most compilers (including those that don't play nicely with C99 enhancements).

#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));

The C specifications prior to C99 do not allow a zero-length array within a structure. To work around this, an array with a single element is created, and one less than the requested element count is added to the size of the actual structure (the size and the first element) to create the intended size.

查看更多
看我几分像从前
5楼-- · 2019-01-12 04:42

Let's keep things short and sweet in C++ using 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);
查看更多
迷人小祖宗
6楼-- · 2019-01-12 04:49

You can also use the "Length 1 Array" trick. This is in 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 also supports "0 length" arrays for exactly this purpose: http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

查看更多
登录 后发表回答