Выделение памяти при вставке в карту

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <vector>
#include <string>
#include <iostream>
#include <map>
#include <utility>
#include <algorithm>

void * GetMemory(size_t n) {
  void *ptr = malloc(n);
  printf("getMem n %d   ptr 0x%x\n", n, reinterpret_cast<unsigned int> (ptr));
  return ptr;
}

void FreeMemory(void *p) {
  free(p);
}

void* operator new (size_t n) {
  void *p = GetMemory(n);
  return p;
}

void* operator new [] (size_t n) {
  void *p = GetMemory(n);
  return p;
}

void operator delete (void *p) {
  FreeMemory(p);
}

void operator delete [] (void *p) {
  FreeMemory(p);
}

typedef std::vector<int> vec;

int main(int argc, char *argv[]) {
  std::map<int, vec> z;
  vec x;
  z.insert(std::pair<int,vec>(1,x));
}

Скомпилировать с помощью g++ -Wall -ansi test.cpp -o test

Запустите тест.

Почему есть три вызова GetMemory с n = 0?


person Prasoon Tiwari    schedule 09.03.2010    source источник


Ответы (2)


Вставьте немного трассировки в FreeMemory и измените main на это:

int main(int argc, char *argv[]) {
  printf("map\n");
  std::map<int, vec> z;
  printf("vec\n");
  vec x;
  printf("pair\n");
  std::pair<int,vec> y(1,x);
  printf("insert\n");
  z.insert(y);
  printf("inserted 1\n");
  y.first = 2;
  printf("insert\n");
  z.insert(y);
  printf("inserted 2\n");

}

Выход:

$ make mapinsert CXXFLAGS=-O3 -B && ./mapinsert
g++ -O3    mapinsert.cpp   -o mapinsert
map
vec
pair
getMem n 0   ptr 0x6b0258
insert
getMem n 0   ptr 0x6b0268
getMem n 32   ptr 0x6b0278
getMem n 0   ptr 0x6b02a0
FreeMemory ptr 0x6b0268
inserted 1
insert
getMem n 0   ptr 0x6b0268
getMem n 32   ptr 0x6b02b0
getMem n 0   ptr 0x6b02d8
FreeMemory ptr 0x6b0268
inserted 2
FreeMemory ptr 0x6b0258
FreeMemory ptr 0x6b02d8
FreeMemory ptr 0x6b02b0
FreeMemory ptr 0x6b02a0
FreeMemory ptr 0x6b0278

Итак, из ваших 3 выделений размером 0:

  • Один из них — скопировать пустой вектор в пару.
  • Один из них — сохранить копию пустого вектора на карте.

Эти два явно необходимы. В чем я не уверен, так это в следующем:

  • Один из них — скопировать вектор где-нибудь в вызове insert, и он также освобождается при вызове вставки.

Это как если бы insert (или что-то, что он вызывает внутри) берет свой параметр по значению, а не по ссылке, или insert явно берет копию в автоматическую переменную за некоторое время до того, как он выделяет новый узел карты. Запуск отладчика в данный момент для меня непростая задача, я оставлю это кому-то другому.

Редактировать: тайна раскрыта. insert принимает std::pair<const int, vec>, а не std::pair<int, vec>. Дополнительная копия пустого вектора связана с тем, что создаваемая вами пара должна быть преобразована в (другой) временный объект, а затем ссылка на этот временный объект передается в insert. std::pair имеет шаблон конструктора, который позволяет вам обойтись почти всем. 20.2.2/4:

template<class U, class V> pair(const pair<U,V> &p);

Эффекты: инициализирует члены из соответствующих членов аргумента, выполняя неявные преобразования по мере необходимости.

Я также заметил, что в моей реализации vec x; не вызывает getMem, а vec x(0); вызывает. Итак, на самом деле:

z[1] = vec();

Является меньшим количеством кода и лишает вас возможности сделать дополнительную копию (хотя вместо этого вызывает operator=). Он по-прежнему делает 2 0-размерных выделений, по крайней мере, для меня.

Стандарт C++ определяет, что operator[] возвращает результат указанного выражения, включающего вызов insert. Я не уверен, означает ли это, что эффекты operator[] "как если бы" были вызваны make_pair и insert (то есть стандарт так же хорош, как и указание источника для operator[]), или просто возвращаемое значение является такое же значение, как заданное выражение. Если последнее, то, возможно, реализация могла бы сделать это с помощью одного выделения размером 0. Но, конечно, map не имеет гарантированного способа создать запись без предварительного создания пары, содержащей сопоставленный тип, поэтому следует ожидать 2 выделения. Или, точнее, 2 копии желаемого сопоставленного значения: тот факт, что копирование вектора размера 0 приводит к выделению размера 0, зависит от реализации.

Итак, если у вас был случай, когда значение было очень дорогим для копирования, но очень дешевым для создания по умолчанию (например, контейнер с большим количеством элементов), то следующее может быть полезным:

std::map<int, vec> z;
vec x(1000);
z[1] = x;
// i.e. (*(z.insert(std::pair<const int, vec>(1,vec())).first)).second = x;

делает 2 выделения размера 4000 и 2 размера 0, тогда как:

std::map<int, vec> z;
vec x(1000);
z.insert(std::pair<const int, vec>(2, x));

составляет 3 размера 4000 и ни одного размера 0. В конце концов размер становится достаточно большим, чтобы дополнительное выделение в первом коде было дешевле, чем дополнительное копирование во втором коде.

Возможно, в этом помогут move-конструкторы в C++0x, я не уверен.

person Community    schedule 09.03.2010
comment
Спасибо за все ваши усилия. Если бы я мог поставить этому ответу *, я бы это сделал. - person Prasoon Tiwari; 09.03.2010
comment
Это хорошая иллюстрация того, почему при вставке таких вещей, как векторы, на карту рекомендуется использовать typedef vector‹T› vec_t; typedef map‹int,vec_t› map_t; vec_t фиктивный; map_t myvals; vec_t tvals(100000,3);/*значение*/ myvals.insert(map_t::value_type(1,dummy)).first-›second.swap(tvals); Это имеет эффект только копирования пустого vec_t для создания узла карты, а затем перемещения значений в него после создания узла (или замены, если он уже существовал). Эквивалентным способом C++0x является myvals.insert(map_t::value_type(1,std::move(tvals))); - person Lance Diduck; 27.03.2010

Все 3 случая связаны с инициализацией пустого вектора:

  1. для инициализации корневого элемента дерева (внутренняя реализация std::map), который будет содержать пустой вектор.
  2. ваша собственная инициализация 'vec x'.
  3. конструктор копирования для std::pair для элемента «второй», который вызывает копирование пустого набора переменной «x»
person Dewfy    schedule 09.03.2010
comment
Кажется, что каждая вставка в карту z вызывает GetMemory() трижды. Попробуйте добавить следующий код внизу: vec y; z.insert(std::pair‹int, vec›(2,y); Вы получаете еще три вызова, все с n = 0. И даже пустой вектор требует 3 указателей: _M_start, _M_finish и _M_end_of_storage. - person Prasoon Tiwari; 09.03.2010
comment
@ prasoon99: И даже для пустого вектора нужно 3 указателя ... - не смешивайте указатели и экземпляры вектора! Указатели не ожидают выделения памяти, а экземпляр вектора. Пустой вектор точно выделяет пустой буфер. В вашем образце это произошло 3 раза - см. мой ответ выше. - person Dewfy; 09.03.2010
comment
Проверьте с помощью gdb. Все вызовы GetMemory() относятся к операторам вставки. Инициализация вектора не вызывает GetMemory(). - person Prasoon Tiwari; 09.03.2010
comment
Кажется, это происходит при вызовах конструктора копирования: vec a; vec b(a); - person visitor; 09.03.2010
comment
Я тоже так думаю. Но почему трижды? - person Prasoon Tiwari; 09.03.2010
comment
Двое из них освобождаются сразу, а другой нет. Таким образом, тот, который не является копией пустого вектора, фактически сохраненного на карте. Я думаю, что два других, вероятно, один для копирования пустого вектора в пару (освобождается в конце оператора, вызывающего insert), а другой для копирования либо пары, либо просто значения где-то в insert. Последнее кажется ненужным. - person Steve Jessop; 09.03.2010
comment
Д'О, я понял это. Смотрите мой ответ. - person Steve Jessop; 09.03.2010