Выделение структуры с членом массива переменной длины - PullRequest
8 голосов
/ 04 октября 2011

Я знаю, что могу сделать new char[n], чтобы создать массив из n символов.Это работает, даже если n не является константой времени компиляции.

Но допустим, я хотел переменную размера, за которой следовало бы n символов:

Моя первая попытка сделать это следующая:

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

Однако, похоже, new Test[n] не выполняет то, что я ожидаю, и вместо этого выделяет n size с.

Я также обнаружил, что sizeof(std::string) равен 4 приideone, поэтому кажется, что он может распределять как размер, так и массив символов в одном блоке.

Есть ли способ, которым я могу достичь того, что я описал (предположительно, что std::string уже делает)?

Ответы [ 5 ]

11 голосов
/ 04 октября 2011

Несмотря на то, что вы можете сделать это (и это часто использовалось в 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));

Спецификации C до C99 не допускают массив нулевой длины в структуре. Чтобы обойти это, создается массив с одним элементом, и к размеру фактической структуры (размер и первый элемент) добавляется единица, меньшая запрошенного числа элементов, для создания требуемого размера.

9 голосов
/ 04 октября 2011

Вы можете использовать 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();
}
7 голосов
/ 04 октября 2011

Вы также можете использовать трюк «Длина 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

2 голосов
/ 04 октября 2011

Если я правильно понимаю, вам нужен класс, в котором хранится один указатель на динамически размещаемую строку с префиксом длины. Вы можете сделать это, воспользовавшись тем, что char* может безопасно создавать псевдонимы.

Упрощенная реализация, просто чтобы показать, как это можно сделать :

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);
    }
};
1 голос
/ 04 октября 2011

Давайте сделаем все коротко и сладко в 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...