插入地图时的内存分配

时间:2010-03-09 10:15:02

标签: c++ memory insert map allocation

#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?

2 个答案:

答案 0 :(得分:8)

在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>。空向量的额外副本是因为您构造的对必须转换为(nother)临时对象,然后对该临时值的引用传递给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=

代码较少,并且拒绝您制作额外副本的机会(尽管它会调用operator[])。它仍然会进行2 0大小的分配,至少对我而言。

C ++标准定义insert以返回涉及调用operator[]的指定表达式的结果。我不确定这是否意味着make_pair的效果“好像”insertoperator[]被调用(也就是说,标准与指定源必须是什么一样好对于map),或者只是返回的值与指定表达式将产生的值相同。如果后者那么也许一个实现可以用一个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个分配。或者更确切地说,是所需映射值的2个副本:复制0大小的向量使得0大小的分配与实现有关。

所以,如果你有一个案例,价值复制真的很贵,但默认构造真的很便宜(比如有很多元素的容器),那么以下内容可能会有用:

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

进行2次分配4000和2的大小为0,而:

{{1}}

使得大小为4000且没有大小为0.最终大小足以使第一个代码中的额外分配比第二个代码中的额外复制便宜。

C ++ 0x中的move-constructors可能对此有所帮助,我不确定。

答案 1 :(得分:6)

所有3个案例都涉及空矢量的初始化:

  1. 到init的根元素(std :: map的内部实现),它将包含空向量。
  2. 您自己初始化'vec x'。
  3. 复制std :: pair的构造函数,用于元素'second'调用复制空集变量'x'