Форум программистов, компьютерный форум CyberForum.ru

realloc и вызов конструктора - C++

Войти
Регистрация
Восстановить пароль
Другие темы раздела
C++ Баг asio? или баг TCP стека? http://www.cyberforum.ru/cpp/thread272413.html
всем привет. повстречался с очень странным багом. и не могу определить кто бажит, asio, или TCP-стек. на стороне клиента, сохраняю отправляемые массивы в файл, чтоб убедится в том, что массивы не портятся. так и есть. массивы не искаженны: на стороне сервера, так же, сохраняю принятые массивы. но тут, массив приходит искаженным:
C++ Получить индекс типа в mpl::map`е всем привет. есть mpl карта типов(key -> value). нужно получить индекс типа имея только ключ. где-то накосячил. но непойму где. #include <iostream> #include <boost/mpl/distance.hpp> #include <boost/mpl/find.hpp> #include <boost/mpl/find_if.hpp> http://www.cyberforum.ru/cpp/thread226098.html
C++ Задачи для тренировки и лучшего понимания
Ребят. Кто-нибудь может дать задачу для тренировки? Приблизительно по всему курсу С++. Буду благодарен за сложную задачу, но которую способен сделать новичок-любитель. Затраты сил-времени не важно. Главное, чтобы это было интересно и не слишком рутинно. + Если найдется человек который даст задачу просьба помогать с кодом, который я буду себя скидывать. Не переписывать за меня, но указывать на...
C++ Теория плагинов
Всем привет. Для одной моей проги, нужно реализовать поддержку плагинов. Плагины предполагаются простенькие, написанные на Си. То, что плагин, это просто .so файл - понятно. То, что прога может дергать из .so файла функции - тоже понятно. 1. Непонятно то, как сам плагин сможет дергать функции из программы? 2. Программа написана на С++, но плагины предполагаю писать на Си, во избежания...
C++ ./massdown: /usr/lib/libstdc++.so.6: version `GLIBCXX_3.4.11' not found (required by ./massdown) http://www.cyberforum.ru/cpp/thread98890.html
всем хай! собираю свою программу на новом железе х86_64 долго не получалось скомпилить. при линковке получал вот такое сообщение: relocation R_X86_64_32 against `a local symbol' я так понял, это из-за того что часть библиотек от которых зависит цель, были собраны как 32-ух битные. но с этим вроде все нормально, пересобрал все либы - скомпилировалось. теперь при запуске получаю такое...
C++ Как понять этот константный указатель? В общем, имеется класс Cat. Объект класса - SimpleCat. И имеется функция с константым указателем: const SimpleCat* const FunctionTwo(const SimpleCat* const theCat) Что обозначают все эти const? Насколько я понимаю: 1. Константный указатель. 2. Может ссылаться только на FunctionTwo 3. Ничего не изменяет в объекте. 4. Ничего не делает с объектом theCat. подробнее

Показать сообщение отдельно
ValeryLaptev
Эксперт С++
1012 / 791 / 46
Регистрация: 30.04.2011
Сообщений: 1,601
25.05.2011, 23:24     realloc и вызов конструктора
Вот дополнительный материал по управлению памятью. Дам в нескольких ответах - много материала.

Свободная память и куча
Динамическая память выделяется объектам во время выполнения программы с помощью специальных операций new и new[][1-5.3.4]. Возврат динамической памяти (и уничтожение объектов при этом) тоже выполняются специальными операциями delete и delete[] [1-5.3.5]. В стандарте [1-12.5] такая динамическая память называется «свободной» памятью — мы ниже рассмотрим подробности работы с ней.

Однако С++ включает и другой вид динамической памяти, о котором в стандарте явно не сказано. Дело в том, что в С++ по наследству от С остались функции malloc(), calloc(), realloc() и free() [1-20.4.6], которые тоже работают с динамической памятью. Первые три из них различными способами выделяют динамическую память, а последняя, естественно, освобождает. Герб Саттер [21] называет динамическую память, с которой работают функции malloc()/free() кучей, как она и называется в стандарте С99 [59].

Стандарт С++ не определяет, должны ли быть реализованы операции new/delete с помощью этих функций. Однако в стандарте явно говорится [1-20.4.6/3/4], что функции malloc()/free() не должны быть реализованы с помощью операций new/delete. Поэтому в общем случае свободная память, с которой работают операции new/delete, и динамическая память, с которой работают функции malloc()/free(), — это «две большие разницы». Возможно, в каком-нибудь компиляторе эти два вида динамической памяти и совпадают, но полагаться на это ни в коем случае нельзя.
Отсюда следует совершенно однозначный вывод: никогда не «смешивайте» эти два механизма управления динамической памятью в своих программах. Этот совет достоин того, чтобы повторить его еще раз!

СОВЕТ
Никогда не «смешивайте» два механизма управления динамической памятью в своих программах.

Память, выделенная одной из операций new, должна возвращаться системе только соответствующей операцией delete, а память, выделенная одной из функций malloc(), calloc(), или realloc(), должна возвращаться только с помощью функции free().

Добавлено через 6 минут
Размещающий new
Существует еще одна форма операции new, которая позволяет разделить «во времени и пространстве» операции выделения памяти и конструирования объекта в этой памяти. Эта форма называется «размещающий» new (placement new) и тоже работает только при подключении заголовка
C++
1
#include <new>
В стандарте [1-18.4] этот вид операции определен так:
C++
1
2
void *operator new(std::size_t size, void *ptr) throw();
void *operator new[](std::size_t size, void *ptr) throw();
Как видите, эта форма оператора тоже исключений не вызывает. Второй аргумент — это указатель на уже выделенный каким-нибудь образом буфер памяти. Действие этой операции похоже на union, но «склеивание» адресов выполняется во время работы программы, а не при компиляции.
В стандарте определены и соответствующие функции delete:
C++
1
2
void operator delete(void* ptr, void*) throw();
void operator delete[](void* ptr, void*) throw();
Однако явным образом их вызывать не требуется.
Рассмотрим несколько простых примеров, чтобы было понятны способы применения этого вида операции new.
C++
1
2
3
4
5
6
int *i = new int[10];                   // выделели память
i[0] = 21;   cout << i[0] << endl;
int *j = new(i) int[10];                // placement new
cout << j[0] << endl;                   // j[0] == i[0] 
j[0] = 22;   cout << i[0] << endl;      // i[0] == j[0]
delete [] i;                            //
Здесь сначала создается динамический массив i, и нулевому элементу присваивается значение 21. Затем размещающий new «приклеивает» по этому же адресу другой динамический массив j. После этого любые изменения в массиве i синхронно изменяют массив j и наоборот. Обратите внимание — у нас два указателя на одну и ту же память, а не два разных массива! Поэтому удалять такой динамический массив нужно только один раз.
Вместо динамического массива мы вполне можем использовать обычный массив, например
C++
1
2
3
4
int i[10] = { 1,2,3,4,5,6,7,8,9,0};     // выделели память
int *j = new(i) int[10];                // placement new
cout << j[0] << endl;                   // j[0] == i[0] 
j[0] = 22;   cout << i[0] << endl;      // i[0] == j[0]
В этом случае размещаемый массив j удалять нельзя. Однако компилятор опять не выдаст никакого сообщения, если программист нечаянно напишет оператор удаления, например
C++
1
delete[] j;
При выполнении опять же может случиться все, что угодно.
Исходный объект и размещаемый, конечно, могут быть разного типа и размера. Но тут нужно быть осторожным, так как компиляторы просто не выдают никаких предупреждающих сообщений .
C++
1
int m; char *pm = new(&m) char;
Этот случай безопасен, так как размещаемая величина меньше по размеру, чем «база». А вот такой вариант
C++
1
char t; double *pt = new(&t) double();
чреват неприятностями. При компиляции сообщений никаких не выдается, поэтому во время работы может произойти все, что угодно. И тут уж как повезет.
Отсутствуют сообщения о преобразовании типов и в случае использования массивов:
C++
1
2
3
int tt[4]={0};  double *p = new(tt)double[4];
double *t = new double[4];  int *j = new (t) int[4];
double m[4];  j = new (m) int[4];
Обратите внимание — в размещающем new в этом случае не нужно указывать операцию взятия адреса &, так как имя массива уже по умолчанию является адресом первого элемента. Конечно, можно «установиться» на любой элемент массива:
C++
1
double *p = new(&tt[1])double[4];
Естественно, в этом случае нужно задавать адрес элемента.
Отсутствие сообщений о том, что типы разные, может спровоцировать ошибки, например:
C++
1
p[0] = 1.2;  cout << p[0] << endl;  cout << tt[0] << endl;
В этом фрагменте мы меняем значение элемента массива типа double, размещенное поверх целого массива. В результате меняется и значение элемента целого массива. Ситуация совершенно аналогична тому, что происходит при использовании union.
Можно поверх скалярной величины разместить массив, и поверх массива — скалярную величину, например
C++
1
2
3
4
5
6
7
int m; char *pm = new(&m) char[sizeof(int)];
char rr[sizeof(int)]; int *ptt = new (rr) int(50); 
Сравните это с объявлением union
union A
{   int m;
    char pm[sizeof(int)];
};
В обоих случаях происходит размещение целой переменной m и символьного массива pm по одному адресу, однако размещаемый new это выполняет во время работы программы, а при использовании union — это делает компилятор.
В принципе, использование размещаемого new в этих случаях эквивалентно использованию обычного указателя, например
C++
1
int *j = &i[0];
Однако такая запись менее заметна (и поэтому провоцирует больше ошибок), чем использование размещающего new.

Размещающий new обычно применяется как раз не для встроенных типов. Еще раз напомним, что работа «обычного» new состоит из двух операций: выделение памяти и вызов конструктора для конструирования объекта. Память размещающий new не выделяет, но вызов конструктора выполняется и в этом случае. Но тогда возникает проблема вызова деструктора — для «обычных» динамических объектов деструктор вызывался автоматически операцией delete. Однако для размещающего new операцию delete выполнять не нужно, поэтому и деструктор автоматически не вызывается!
Решение очень простое: нужно вызвать деструктор явно [1-12.4/13]. Рассмотрим пример (листинг 8.5).
C++
1
2
3
4
5
6
7
8
9
10
11
Листинг 8.5. Явный вызов деструктора
class Integer                           // некоторый класс
{ int i;
  public: 
    Integer() { cout << "constructor" << endl; }
    ~Integer() { cout << "destructor" << endl; }
};
// …
int m;                              // память для объекта
Integer *pi = new(&m) Integer;      // размещение объекта
pi->~Integer();                     // разрушение объекта
Мы объявили простой класс, размер которого равен размеру целого. Размещение делается обычным способом. А последняя строка как раз и является явным вызовом деструктора. Обратите внимание, что и в данном случае преобразования типов не требуется.
Заменим в классе Integer конструктор по умолчанию конструктором инициализации:
C++
1
Integer(int k=0):i(k) { }
Тогда можно задавать размещение объекта с инициализацией, например
C++
1
Integer *pi = new(&m) Integer(6);
Если уж нам приходится «работать компилятором», то нужно помнить, что при уничтожении массива требуется вызывать деструктор для каждого его элемента.
C++
1
2
3
int tt[4]; 
pi = new(tt) Integer[4];                
for(int i = 0; i < 4; ++i) (pi+i)->~Integer();
При размещении массива типа Integer каждый из элементов инициализируется 0, поскольку вызывается конструктор инициализации с аргументом по умолчанию.
Вызов деструктора написан в указательном стиле. Можно использовать и привычный синтаксис с индексом, например
C++
1
for (int k = 0; k < 4; ++k) pi[k].~Integer();
До сих пор мы в качестве «базы» размещаемого new использовали только встроенные типы, однако это совершенно не обязательное условие — тип «основы» может быть любым. Например, определим небольшой шаблон
C++
1
2
3
4
template <class T, int size = sizeof(T)>
class DynamicUnion
{   char s[size];
};
При инстанцировании объект конкретного типа займет ровно столько байт памяти, каков размер типа T. Теперь мы можем использовать этот шаблон для размещения объектов типа Integer
C++
1
2
3
DynamicUnion<Integer> t;
Integer *pt = new(&t) Integer(7);
pt->~Integer();
Деструктор можно вызывать и так
C++
1
(*pt).~Integer();
Разрешается «разделить» запрос памяти и конструирование в ней объекта, например
C++
1
2
3
4
5
void *p = new DynamicUnion<Integer>;    // выделили sizeof(Integer) байт  
new (p) Integer(7);                     // разместили объект  
((Integer*)p)->~Integer();              // удалили объект
Оператор
new (p) Integer(7);                     // разместили объект
представляет собой «объявление» объекта типа Integer. Вызывается конструктор класса Integer и размещает поля объекта по указанному адресу, выполняя их инициализацию.
В данном случае при выделении памяти использован бестиповый указатель. Ни запрос памяти, ни размещение по этому адресу объекта не требуют преобразования типа. А вот разрушение объекта без преобразования указателя вызывает протесты компилятора — C++ Builder 6 выдает ошибку E2288:
C++
1
Pointer to structure required on left side of -> or ->*
Слева от -> или ->* требуется указатель на структуру
Поскольку р не является типизированным указателем на структуру, «возмущение» компилятора совершенно обосновано.
Точно так же с явным преобразованием типа необходимо обращаться к методам размещенного объекта. Пусть, например, в классе Integer определен метод:
C++
1
2
void print() 
{ cout << "print()="<< i << endl; }
Тогда обращение к нему должно выглядеть так:
C++
1
((Integer*)p)->print();
Если мы при запросе памяти объявим указатель типа Integer, то при обращении, естественно, преобразования не потребуется, например
C++
1
2
3
4
Integer *p = (Integer*)new DynamicUnion<Integer>;   // преобразование здесь
new (p) Integer(15);                                // размещение объекта
p->print();
p->~Integer();
Обратите внимание, что этом фрагменте мы имеем единственный указатель, получивший адрес объекта в динамической памяти. Можно обойтись вообще без динамической памяти, например
C++
1
2
int m; 
new(&m) Integer(6);
Объект типа Integer создан и проинициализирован по месту переменной m. Тогда возникает вопрос: как же тогда вызывать деструктор такого объекта? А вот так:
C++
1
((Integer*)&m)->~Integer();
или так
C++
1
(*(Integer*)&m).~Integer();
К сожалению, с массивами размещающий new не всегда работает правильно (хотя это могут быть недоработки конкретных компиляторов). Рассмотрим простейший пример
C++
1
2
3
int tt[4]; 
Integer *pi = new(tt) Integer[4];   // корректный вызов конструктора
new(tt) Integer[4];                 // некорректный вызов конструктора
Вариант с присвоением адреса указателю pi работает совершенно правильно, вызывая четыре конструктора со значением по умолчанию, равному 0. А вот второй вариант инициализирует первый элемент массива значением 4, а остальные — обнуляет.
 
Текущее время: 19:49. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru