Как перераспределить память, используя new для выделения переменных внутри структуры?

Итак, у меня есть пара структур...

struct myBaseStruct
{
};

struct myDerivedStruct : public myBaseStruct
{
    int a, b, c, d;
    unsigned char* ident;
};

myDerivedStruct* pNewStruct;

... и я хочу динамически выделять достаточно места, чтобы я мог "memcpy" в некоторых данных, включая строку с нулевым завершением. Размер базовой структуры, по-видимому, равен «1» (я предполагаю, что он не может быть равен нулю), а размер производной структуры равен 20, что кажется логичным (5 x 4).

Итак, у меня есть буфер данных размером 29, первые 16 байтов — это целые числа, а остальные 13 — строка.

Как выделить достаточно памяти для pNewStruct, чтобы хватило на строку? В идеале я просто хочу пойти:

  • выделить 29 байт в pNewStruct;
  • memcpy из буфера в pNewStruct;

Спасибо,


person acron    schedule 16.12.2009    source источник
comment
Хорошо, спасибо за отзыв. Меня соблазняет идея malloc, но я не знаком с парадигмами, которые предполагают, что это плохая идея? Во что бы то ни стало называйте меня n00b — в качестве альтернативы, некоторые ссылки на документацию, детализирующую это, были бы гораздо более продуктивными.   -  person acron    schedule 17.12.2009
comment
Если вам нужна непрерывная структура переменной длины, вы должны поместить в конец массив, а не указатель.   -  person Alex B    schedule 17.12.2009
comment
Спасибо. Да, основываясь на ответе Ремуса, это то, что я сделал, и в сочетании с использованием malloc я заставил его работать. Чтобы уточнить, причина, по которой структура является производной, заключается в том, что я могу сохранить указатель неспецифического типа, а затем просто memcpy в цикле. На самом деле процесс делает следующее: - Считывает байт блочного типа. - Читает размер блока. - Преобразует переменную-член правильного типа в расширенный размер. - Использует локальный указатель базового класса для указания на переменную-член. - Memcpy указывает размер указателя. Это работает в цикле, основанном на незнании того, какой блок следует за ним.   -  person acron    schedule 17.12.2009
comment
Очень важно, не используйте memcpy, если вы не можете гарантировать, что компилятор поместит элементы в одно и то же место между целевой и целевой структурами. Компиляторам разрешено добавлять отступы между элементами без уведомления пользователя. Самый безопасный и переносимый метод — использовать копирование по элементам либо с помощью конструктора копирования, либо с помощью функции.   -  person Thomas Matthews    schedule 17.12.2009
comment
Если два класса POD имеют одни и те же элементы данных в одном и том же порядке, то они совместимы с макетом (9.2/14). Хотя я не могу найти часть спецификации, которая говорит об этом, я почти уверен, что это означает именно то, что компилятор должен поместить члены в одно и то же место, чтобы вы могли memcpy от одного к другому. Если бы в этом случае не было базового класса, то подходили бы два разных класса/структуры с членами, указанными в вопросе. Проблема в том, что базовому классу разрешено занимать несколько байтов.   -  person Steve Jessop    schedule 17.12.2009


Ответы (9)


Вы можете выделить любой размер с помощью malloc:

myDerivedStruct* pNewStruct = (myDerivedStruct*) malloc(
      sizeof(myDerivedStruct) + sizeof_extra data);

Однако у вас другая проблема, поскольку myDerivedStruct::ident является очень неоднозначной конструкцией. Это указатель на символ (массив), тогда структуры заканчиваются адресом, где начинается массив символов? ident может указывать куда угодно и очень неоднозначно, кто владеет массивом, на который указывает ident. Мне кажется, что вы ожидаете, что структура закончится самим фактическим массивом символов, а структура владеет дополнительным массивом. Такие структуры обычно имеют член size для отслеживания их собственного размера, чтобы API-функции могли правильно управлять ими и копировать их, а дополнительные данные по соглашению начинаются после завершения структуры. Или они заканчиваются массивом нулевой длины char ident[0], хотя это создает проблемы для некоторых компиляторов. По многим причинам в таких структурах нет места наследованию:

struct myStruct 
{
size_t size;    
int a, b, c, d;    
char ident[0];
};
person Remus Rusanu    schedule 16.12.2009
comment
Объявления массивов размера 0 некорректны в C++ (как и в C, BTW). У всех компиляторов есть проблемы с этим, а не только у некоторых. - person AnT; 17.12.2009
comment
Да, поэтому, исходя из предположения, что каждая строка будет иметь как минимум 1 символ, я использовал «идентификатор символа;». Этот метод сработал. - person acron; 17.12.2009
comment
'идентификатор символа;'? В чем смысл? Как вы собираетесь получить доступ к строке? Применять & к ident каждый раз? Это сработает, но какой в ​​этом смысл? - person AnT; 17.12.2009
comment
Зачем мне добавлять &? Мне не нужен адрес ident.. ? Память выделена, и этот символ является первым символом в расширенном пространстве. Поэтому я использовал ident[0] до тех пор, пока ident[n] == '\0'. Я ценю, что stl::string избавит вас от многих низкоуровневых придирок, но я не знаю, как бы я применил их в этом контексте и с той эффективностью, которую я пытаюсь достичь. - person acron; 17.12.2009
comment
@AndreyT: GNU C позволяет использовать этот массив нулевой длины в качестве расширения. Не уверен, означает ли это, что у него проблемы с ним. Он запрещает это в педантичном режиме. C99 допускает массив гибкой длины в конце структуры char ident[], предназначенной для этой цели. - person Steve Jessop; 17.12.2009
comment
Старые компиляторы допускали массивы нулевой длины для «взлома структуры». Старые системные заголовки были заполнены структурами, заканчивающимися массивами нулевой длины. Более новые компиляторы явно запрещают это, а некоторые принимают новый синтаксис безразмерного массива. Я думаю, что означает «старый» и «новый» относительно... Это также обсуждается stackoverflow.com/questions/627364/ - person Remus Rusanu; 17.12.2009
comment
Поскольку он использует 0 завершающую строку в своей структуре, он должен установить размер массива равным 1. Тогда ему не нужен +1 после вызова strlen(). В любом случае, это вовсе не решение C++. - person jmucchiello; 17.12.2009
comment
@Steve Jessop: я знаю, что GCC это позволяет. Все же вопрос о C, а не о GCC. В C99 это [], а не [0], что не одно и то же. - person AnT; 17.12.2009
comment
@Remus Rusanu: Struct hack не требует массива нулевой длины. Массив любой длины будет работать так же хорошо. Правильная форма struct hack обычно использует массив размером 1, как я использовал в своем ответе. Массивы размера 0 для struct hack — типичный признак грязного кода. Известно, что некоторые люди используют размер 0, поскольку он упрощает (не совсем) расчет общего размера, но я не принимаю это как оправдание. Как бы то ни было, канонический struct hack использует массив размером 1, а не 0. - person AnT; 17.12.2009
comment
@acron: А? Вы сказали, что использовали char ident;. Такой ident не является массивом. По этой причине вы не можете использовать ident[0] или ident[n]. То, что вы говорите, не имеет смысла. Просьба уточнить. - person AnT; 17.12.2009

Вы возвращаетесь к C или отказываетесь от этих идей и фактически используете C++ по назначению.

  • Используйте конструктор для выделения памяти и деструктор для ее удаления.
  • Не позволяйте другому коду записывать в ваше пространство памяти, создайте функцию, которая обеспечит выделение памяти.
  • Используйте std:string или std::vector для хранения данных вместо создания собственного класса контейнера.

В идеале вы должны просто сказать:

myDerivedClass* foo = новый myDerivedClass(a, b, c, d, ident);

person jmucchiello    schedule 16.12.2009
comment
Я мог бы сделать это, но это сильно снижает эффективность. Кроме того, входящий буфер имеет хорошо упакованные данные - какой смысл разделять их все только для того, чтобы снова собрать? Все, что я хочу сделать, это скопировать данные из буфера и придать им некоторый контекст, используя мою структуру. - person acron; 17.12.2009
comment
@acron: Спросите себя, так ли важна эффективность настолько, что вы не можете позволить себе писать надежный, читаемый код на C++. Если вы определили, что это так, рассмотрите возможность использования C, как было предложено. - person Steve S; 17.12.2009

В текущем стандарте C++ myDerivedStruct не является POD, поскольку у него есть базовый класс. Результат memcpyвставки чего-либо в него не определен.

Я слышал, что C++0x ослабит правила, так что больше классов будут POD, чем в C++98, но я не изучал это. Кроме того, я сомневаюсь, что очень многие компиляторы разместят ваш класс таким образом, который несовместим с POD. Я ожидаю, что у вас будут проблемы только с тем, что не выполняет оптимизацию пустого базового класса. Но вот оно.

Если бы это был POD или если вы готовы рискнуть своей реализацией, то вы могли бы использовать malloc(sizeof(myStruct)+13) или new char[sizeof(myStruct)+13] для выделения достаточного пространства, в основном так же, как и в C. По-видимому, мотивация состоит в том, чтобы избежать памяти и времени. накладные расходы на то, чтобы просто поместить член std::string в свой класс, но за счет необходимости писать код для ручного управления памятью.

person Steve Jessop    schedule 16.12.2009
comment
Навскидку, я не думаю, что это правда. У него нет виртуальных функций или пользовательского конструктора по умолчанию, поэтому я думаю, что это POD. - person James; 17.12.2009
comment
Навскидку, 9/4: структура POD — это агрегатный класс, а 8.5.1/1: агрегат — это массив или класс без… базовых классов. Хорошо, я солгал, что это не пришло мне в голову ;-) - person Steve Jessop; 17.12.2009
comment
@Steve: Тогда почему компиляторы (GCC), которые жалуются на определенные операции в классах, отличных от POD, не жалуются на простые производные структуры? - person Zan Lynx; 17.12.2009
comment
@Steve: В частности, я могу объявить их массивы для наложения данных mmap() без запуска конструкторов по умолчанию. - person Zan Lynx; 17.12.2009
comment
Я ничего не знаю об этом конкретном предупреждении GCC, поэтому не могу его объяснить, извините. Поскольку вы говорите, что предупреждение касается конструкторов по умолчанию, я бы предположил, что, возможно, это как-то связано с тем фактом, что конструктор myDerivedStruct по-прежнему ничего не делает в GCC. Если вы используете offsetof с myDerivedStruct, вы получите предупреждение от GCC, но оно все равно даст правильный ответ. - person Steve Jessop; 17.12.2009

Вы можете перераспределить ресурсы для любого экземпляра класса, но это требует определенных затрат на управление. Единственный допустимый способ сделать это — использовать специальный вызов выделения памяти. Это можно сделать без изменения определения класса.

void* pMem = ::operator new(sizeof(myDerivedStruct) + n);
myDerivedStruct* pObject = new (pMem) myDerivedStruct;

Предполагая, что вы не перегружаете operator delete в иерархии, тогда delete pObject будет правильным способом уничтожить pObject и освободить выделенную память. Конечно, если вы выделяете какие-либо объекты в области избыточной памяти, вы должны правильно освободить их перед освобождением памяти.

После этого у вас есть доступ к n байтам необработанной памяти по этому адресу: void* p = pObject + 1. Вы можете memcpy передавать данные в эту область и из нее по своему усмотрению. Вы можете присвоить самому объекту и не должны memcpy его данные.

Вы также можете предоставить настраиваемый распределитель памяти в самом классе, который требует дополнительных size_t, описывающих объем избыточной памяти для выделения, что позволяет вам выполнять выделение в одном выражении new, но это требует дополнительных накладных расходов при разработке класса.

myDerivedStruct* pObject = new (n) myDerivedStruct;

и

struct myDerivedStruct
{
    // ...
    void* operator new(std::size_t objsize, std::size_t excess storage);

    // other operator new and delete overrides to make sure that you have no memory leaks
};
person CB Bailey    schedule 17.12.2009

В этом контексте смешивание memcpy и new кажется ужасной идеей. Вместо этого рассмотрите возможность использования malloc.

person Cory Petosky    schedule 16.12.2009
comment
Я думаю, что это, вероятно, лучшая идея. Д'о. - person acron; 17.12.2009

Вы можете динамически распределять пространство, выполнив:

myDerivedStruct* pNewStruct = reinterpret_cast<myDerivedStruct*>(new char[size]);

тем не мение

Вы уверены, что хотите это сделать?

Также обратите внимание, что если вы собираетесь использовать ident в качестве указателя на начало вашей строки, это будет неправильно. На самом деле вам нужен &ident, так как переменная ident находится в начале вашего неиспользуемого пространства, интерпретация того, что в этом пространстве, как указателя, скорее всего, будет бессмысленной. Следовательно, было бы более разумно, если бы ident был unsigned char или char, а не unsigned char*.

[редактировать снова] Я просто хотел бы подчеркнуть, что то, что вы делаете, на самом деле очень плохая идея.

person James    schedule 16.12.2009
comment
О Боже, пожалуйста, останови боль! - person Nikola Smiljanić; 17.12.2009
comment
Потому что: - Вам придется всегда выделять всю память для этих структур самостоятельно, иначе вы в конечном итоге испортите память. - Значения полей вашей структуры, когда вы выполняете memcpy, будут зависеть от платформы (наиболее очевидно, что они будут зависеть от порядка следования байтов и размера int). - sizeof(myDerivedStruct) будет вводить в заблуждение - Ваш код будет очень запутанным для чтения, и любой, кто будет поддерживать его в будущем, может не понять, что вы делаете, даже если вы это сделаете. - person James; 17.12.2009

char* buffer = [some data here];
myDerivedStruct* pNewStruct = new myDerivedStruct();
memcpy(buffer,pNewStruct,4*sizeof(int));
pNewStruct->ident = new char[ strlen(buffer+(4*sizeof int)) ];
strcpy(pNewStruct->ident,buffer+(4*sizeof int));

Что-то такое.

person rossipedia    schedule 16.12.2009
comment
Я хотел бы отказаться от того, что делать что-то таким образом - плохая идея (tm). Если вы используете С++, вы должны использовать std::string - person rossipedia; 17.12.2009
comment
К сожалению, STL не считается оптимальным для платформы, на которой я работаю:/ - person acron; 17.12.2009

Известен ли размер буфера во время компиляции? В этом случае статически выделенный массив будет более простым решением. В противном случае см. ответ Ремуса Русану выше. Вот как Win32 API управляет структурами переменного размера.

struct myDerivedStruct : public myBaseStruct
{
    int a, b, c, d;
    unsigned char ident[BUFFER_SIZE];
};
person christopher_f    schedule 16.12.2009

Во-первых, я не понимаю, какой смысл иметь базу myBaseStruct. Вы не дали никаких объяснений.

Во-вторых, то, что вы заявили в своем исходном сообщении, не будет работать с описанным вами макетом данных. Для того, что вы описали в OP, вам нужно, чтобы последний член структуры был массивом, а не указателем.

struct myDerivedStruct : public myBaseStruct {
    int a, b, c, d;
    unsigned char ident[1];
};

Размер массива не имеет значения, но он должен быть больше 0. Массивы размера 0 явно недопустимы в C++.

В-третьих, если вы по какой-то причине хотите использовать именно new, вам придется выделить буфер из char объектов требуемого размера, а затем преобразовать результирующий указатель в ваш тип указателя.

char *raw_buffer = new char[29];
myDerivedStruct* pNewStruct = reinterpret_cast<myDerivedStruct*>(raw_buffer);

После этого вы можете сделать свой memcpy, предполагая, что размер правильный.

person AnT    schedule 16.12.2009