Использование malloc / realloc для массива классов / структур, включая вектор std

У меня есть вопрос относительно памяти malloc / realloc, которая будет содержать массив членов класса / структуры (я пробовал и структуру, и класс, проблема остается), которые включают векторы std. Я знаю, что могу обойти проблему, используя новый класс контейнера std и массив. Однако я хотел бы лучше понять, почему следующий небольшой код вылетает, когда я использую realloc вместо malloc (поскольку я столкнулся с этой проблемой в контексте перехода более крупного проекта кода с C на C ++). Кажется также, что я не могу обязательно установить начальный размер вектора в классе / структуре (некоторые компиляторы допускают, а некоторые нет ..) - так что же такое вектор в классе - удобный указатель?

Спасибо Кай

#include <stdlib.h>
#include <limits.h>
#include <float.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <vector>
/* mpic++ -O3 -ffast-math -pedantic vec-alloc.cpp -o vec-alloc */

using namespace std;

class float_vector{
public:
  double x;
  double y;
  double z;
  float_vector() : x(0), y(0), z(0) {};
};


class voxel{
public:
  float_vector   x;
  vector<double> y;

  voxel() : x() {};
};

int main(){

  int i;
  double d =1.111;
  voxel v0, *Comp, *Comp2;

  /* dynamically allocate memory */
  Comp= (voxel*)malloc(10*sizeof(voxel));
  for(i=0;i<10;++i) Comp[i] = v0;
  printf("malloc done\n");

  /* dynamically re-allocate memory */
  Comp2= (voxel*)malloc(sizeof(voxel));  
  printf("realloc done\n");
  for(i=0;i<10;++i){
    Comp2 =(voxel*)realloc(&Comp2[0], (i+1)*sizeof(voxel));
    Comp2[i] = v0;
  }  

  printf("realloc done\n");

  for(i=0;i<10;++i) Comp[i].y.push_back(d);
  for(i=0;i<10;++i) printf("%lf\n",Comp[i].y[0]);

  for(i=0;i<10;++i) Comp2[i].y.push_back(d); // this crashes
  for(i=0;i<10;++i) printf("%lf\n",Comp2[i].y[0]);

  return 1;
} 
 Ed Heal06 авг. 2016 г., 20:21
1. Почему вы используетеusing namespace std; - этоплохая идея, 2 Почему вы используетеmalloc и неnew
 Steve Jessop06 авг. 2016 г., 20:22
@EdHeal: почему вы просматриваете код и не отвечаете на вопрос? ;-)
 user26839606 авг. 2016 г., 20:27
Это может быть связано с его происхождением как кода C, как указано в вопросе. :)
 Ed Heal06 авг. 2016 г., 20:24
Потому что начало хорошего кода состоит в том, чтобы исправить эти биты в начале. Похоже, что этот код более или менее C-код с намеком на C ++
 Ed Heal06 авг. 2016 г., 20:22
И почему вы используетеprintf и неcout?
 Ed Heal06 авг. 2016 г., 20:28
... Переход с C на C ++ не является хорошей идеей, чтобы сделать по частям.

Ответы на вопрос(3)

realloc, Ваш код уже имеет неопределенное поведение, когда вы делаете это в начале:

for(i=0;i<10;++i) Comp[i] = v0;

Comp[0] никогда не был инициализирован (так какmalloc возвращает неинициализированную память - она ​​не может знать, для какого типа вы собираетесь ее использовать, и поэтому не может инициализировать ее, даже если бы захотела). Затем ваш код пытается присвоить ему. Это не разрешено для сложных типов, таких какvector.

Почему это не разрешено? В случае вектора, потому что, когда вы назначаете вектор, который уже содержит данные, он должен освободить старые данные. Если нечего освобождать, то это ничего не освободит. Но неинициализированная память может иметь какие-либо значения вообще, поэтому вполне можетvector что есть что-то, что должно быть освобождено, что на самом деле вообще не является свободным указателем, не говоря уже о том, чтоvector должен быть освобожден как следствие этого назначения. Без инициализации некоторый инвариант класса по строкам «этот элемент данных указателя всегда является либо нулевым указателем, либо это адрес некоторой памяти, за который отвечает вектор», и поэтомуvector код не работает.

Предположим, что ваш код каким-то образом преодолел эту точку, вы все равно не можетеrealloc память, содержащаяvector, С точки зрения стандарта, это потому, чтоvector<double> не является POD-типом, и поэтому его побайтные копии (в том числе сделанныеrealloc) привести к неопределенному поведению.

С точки зрения конкретной реализации, мы могли бы спросить себя, какой код мог бы написать разработчик, что может пойти не так, если векторы копируются побайтово. Один гипотетический ответ заключается в том, что в некоторых обстоятельствахvector может содержать указатель на собственное тело (как часть так называемой малой векторной оптимизации) не POD, разработчик может свободно использовать свое творчество]. Если вектор перемещен, то этот указатель больше не указывает на собственное тело вектора, поэтому инварианты класса не выполняются, и код больше не работает. Чтобы дать разработчикам свободу в написании подобного кода, ваша свобода как пользователя класса ограничена, и вам не разрешается перемещать вектор (или вообще любой не-POD тип) путем побайтового копирования.

 Steve Jessop08 авг. 2016 г., 11:19
@Kai: вместо перераспределения массива вокселей используйтеvector<voxel>. vector знает, как правильно расширять свой массив при необходимости.
 Kai 06 авг. 2016 г., 22:54
Большое спасибо за информацию. Теперь я понимаю проблему с не POD и malloc - я привык к C и сборке и пытаюсь улучшить большой код C (> 15 000 строк, 30 модулей) и думал, что вектор std поможет - добавил некоторые функции C ++ и сейчас пробуют более важные, такие как вектор, плохая идея? Я, конечно, могу легко удалить все malloc и sub с помощью new, но я использую realloc всякий раз, когда читаю в файлах данных с неизвестной длиной, не уверенный, как это сделать, а затем без realloc (если я сначала не сканирую, чтобы проверить, сколько данных), предложения? Спасибо Кай
/* dynamically allocate memory */
Comp= (voxel*)malloc(10*sizeof(voxel));

Comp теперь указатель на неинициализированную память.

Это пытается позвонитьComp[i].operator=(v0), ноComp[i] не является допустимым, инициализированным объектом. В простом случае тестирования / отладки нам может повезти, но на практике мы получим мусор, и вектор либо попытается освободить / использовать неверный указатель.

Это не значит, что вы должныcalloc() вместо памяти вы не можете делать предположения о том, какие значения ожидает найти инициализированный объект.

/* dynamically re-allocate memory */
Comp2= (voxel*)malloc(sizeof(voxel));  
printf("realloc done\n");

Comp2 теперь указатель наодин воксельи "realloc" не было сделано.

for(i=0;i<10;++i){
  Comp2 =(voxel*)realloc(&Comp2[0], (i+1)*sizeof(voxel));
  Comp2[i] = v0;
}  

Это просто странно. Он начинается с того, что Comp2 указывает на один воксель. Тогда вы почему-то берете адрес первого элемента (&Comp2[0]) вместо того, чтобы просто использовать адрес первого элемента (Comp2), и вы перераспределяете его на тот же размер. Затем вы копируете-назначаете v0 в неинициализированную память в последней позиции, кроме одной:

Comp2 = [...uninit...]

for (i  = 0
realloc(i + 1 == 1)

Comp2 = [...uninit...]
              ^-- v0

i++
realloc(i+1 == 2)

Comp2 = [.....v0.....][...uninit...]
                            ^--v0

Коротко: вы не можете использоватьmalloc или жеcalloc или жеrealloc с не под-объектами. Иногда это может сойти с рук, но вы в основном направляете заряженное ружье на ногу.

Также кажется, что я не могу обязательно установить начальный размер вектора в классе / структуре

Вы можете легко установить размер вектора по умолчанию в классе, C ++ 11 требуется (-std=c++11 или выше для компиляторов gnu / clang, VS2013 или выше)

#include <iostream>
#include <vector>

struct A {
    std::vector<int> v = { 1, 2, 3 }; // default population
};

struct B {
    std::vector<int> v;
    B() : v(4) {}
};

int main() {
    A a;
    B b;
    std::cout << a.v.size() << ", " << b.v.size() << "\n";
    std::cout << "\n";
    for (int v : a.v) { std::cout << v << "\n"; }
    std::cout << "\n";
    for (int v : b.v) { std::cout << v << "\n"; }
}

http://ideone.com/KA9fWB

 Steve Jessop06 авг. 2016 г., 21:27
«на данный момент - теперь он указывает на массив, достаточно большой, чтобы вместить 2 вокселя» -i начинается в0, поэтому в первый раз в цикле он выделяет место для одного вокселя и присваивает ему (с индексом 0). Второй раз он выделяет место для двоих и присваивает второй (по индексу 1).
 kfsone06 авг. 2016 г., 21:30
@ SteveJessop Хорошо, я придавал слишком большое значение sizeof.

malloc() с не POD классами вы должны вызывать конструкторы (через размещениеnew) и деструкторы вручную.

Использование объекта, который не был построен должным образом, приводит кнеопределенное поведение, что часто означает сбой, когда дело доходит до указателей.

Очевидно, что освобождение памяти для объекта без надлежащего уничтожения также вызывает UB.

Ваш код должен выглядеть так:

MyClass *arr = (MyClass *) malloc(10 * sizeof (MyClass));

for (int i = 0; i < 10; i++)
    new (arr + i) MyClass; // This line calls constructors

// Do something with the array here

for (int i = 0; i < 10; i++)
    arr[i].~MyClass(); // This line calls destructors.

free(arr);

Это требование также означает, что вы не можете использоватьrealloc() с не POD-типами, потому что он не вызовет деструкторы для старого массива и конструкторы для нового для вас.

Код перераспределения вручную может выглядеть так:

MyClass *new_ptr = (MyClass *) malloc(new_size * sizeof (MyClass));

for (int i = 0; i < new_size; i++)
    new (new_ptr + i) MyClass((MyClass &&) old_ptr[i]);

for (int i = new_size; i < old_size; i++)
    new (new_ptr + i) MyClass;

for (int i = 0; i < old_size; i++)
    old_ptr[i].~MyClass();

free(old_ptr);

И имейте в виду, что приведенный выше код не является безопасным для исключений. Если конструктор выдает исключение, и вы его перехватываете, то вы хотите быть уверены, что правильно уничтожили объекты, которые были построены.Спасибо @SteveJessop.

Теперь, когда вы понимаете, почемуmalloc()/free() обычно следует избегать в C ++, я надеюсь, что вы вернетесь к гораздо более безопаснымnew/delete, которые делают все это строительство и разрушение для вас.

 Steve Jessop06 авг. 2016 г., 20:37
Вы даете правильное общее представление, но требуемый код еще хуже, чем тот, так как он должен вести себя правильно в случае, когда один из 10 вызовов конструктора выдает исключение (и уничтожает, сколько из них уже было построено до этого момента). Лучше всего избегать.

Ваш ответ на вопрос