简体   繁体   中英

Memory Allocation by STL C++ Objects

I am using malloc_stats() function to display the amount of "system bytes" and "in use" bytes used by the process. I wanted to know if the in use bytes also include the memory used by STL C++ Objects like map, vector, sets?

If yes, is it safe to assume that this is only amount of memory that will be used by the process?

That is not specified in the standard, new may call malloc or not. In practice in the implementations I have worked with, it does.

The best way to do what you want is to define your own new operator that accounts for allocated memory, or use an accounting allocator for STL containers like this one:

#include <memory>
#include <limits>
extern size_t allocated;
template <class T>
class accounting_allocator {
public:
    // type definitions
    typedef T        value_type;
    typedef T*       pointer;
    typedef const T* const_pointer;
    typedef T&       reference;
    typedef const T& const_reference;
    typedef std::size_t    size_type;
    typedef std::ptrdiff_t difference_type;
    //static size_t allocated;

    // rebind allocator to type U
    template <class U>
    struct rebind {
        typedef accounting_allocator<U> other;
    };

    // return address of values
    pointer address (reference value) const {
        return &value;
    }
    const_pointer address (const_reference value) const {
        return &value;
    }

    /* constructors and destructor
     * - nothing to do because the allocator has no state
     */
    accounting_allocator() throw() {
    }
    accounting_allocator(const accounting_allocator&) throw() {
    }
    template <class U>
      accounting_allocator (const accounting_allocator<U>&) throw() {
    }
    ~accounting_allocator() throw() {
    }

    // return maximum number of elements that can be allocated
    size_type max_size () const throw() {
    //  std::cout << "max_size()" << std::endl;
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }

    // allocate but don't initialize num elements of type T
    pointer allocate (size_type num, const void* = 0) {
        // print message and allocate memory with global new
        //std::cerr << "allocate " << num << " element(s)" << " of size " << sizeof(T) << std::endl;
        pointer ret = (pointer)(::operator new(num*sizeof(T)));
        //std::cerr << " allocated at: " << (void*)ret << std::endl;
       allocated += num * sizeof(T);
        //std::cerr << "allocated: " << allocated/(1024*1024) << " MB" << endl;
        return ret;
    }

    // initialize elements of allocated storage p with value value
    void construct (pointer p, const T& value) {
        // initialize memory with placement new
        new((void*)p)T(value);
    }

    // destroy elements of initialized storage p
    void destroy (pointer p) {
        // destroy objects by calling their destructor
        p->~T();
    }

    // deallocate storage p of deleted elements
    void deallocate (pointer p, size_type num) {
        // print message and deallocate memory with global delete
 #if 0
        std::cerr << "deallocate " << num << " element(s)"
                  << " of size " << sizeof(T)
                  << " at: " << (void*)p << std::endl;
 #endif
        ::operator delete((void*)p);
       allocated -= num * sizeof(T);
    }
};

#if 0
template<>
class accounting_allocator<void>
{
public:
    typedef size_t      size_type;
    typedef ptrdiff_t   difference_type;
    typedef void*       pointer;
    typedef const void* const_pointer;
    typedef void        value_type;
    template<typename _Tp1>
    struct rebind  { typedef allocator<_Tp1> other; };
};
#endif

// return that all specializations of this allocator are interchangeable
template <class T1, class T2>
bool operator== (const accounting_allocator<T1>&,
                 const accounting_allocator<T2>&) throw() {
    return true;
}
template <class T1, class T2>
bool operator!= (const accounting_allocator<T1>&,
                 const accounting_allocator<T2>&) throw() {
    return false;
}
/************** accounting allocator end ***********/

An example of accounting with new: Although I'd prefer using (3) malloc_hook and an unordered_map

size_t used_mem = 0;
std::map<void*,size_t> acc;

void* operator new(size_t size) throw (std::bad_alloc)
{
    void *mem;
    if(size == 0)
        size = 1;
    mem = malloc(size);
    if(mem) {
        memset(mem, 0, size);
        used_mem += size;
        acc[mem] = size;
        return mem;
    }
    std::new_handler nh = std::set_new_handler(0);
    std::set_new_handler(nh);
    if (nh) {
        (*nh)();
        return (void*)0;
    } else {
        //return (void*)0;
        throw std::bad_alloc();
    }
}

void operator delete(void *mem) throw()
{
    std::map<void*,size_t>::iterator i = acc.find(mem);
    if( i != acc.end() ) {
        size_t size = i->second;
        used_mem -= size;
    }
    free(mem);
}

The STL is not required to use malloc by default, but you can pass in an allocator to specify this. See this example at Dr. Dobbs.

I don't have a malloc_stats function on my system, but you could run a simple test by creating a vector of 1000000 int s or something (just be sure you save all your work just in case you accidentally use up all your RAM and crash your system :-) ).

Note that on many OSes, malloc is not the only way to allocate memory to a program. On Linux, for instance, you (or a C++ implementation) can skip malloc() and use lower-level system functions like mmap() and sbrk() . Thus, malloc_stats might not tell you all of the memory a program is using. Check out getrusage for that.

You can't always assume that the default allocator and operator new use malloc , but it's pretty likely that all come from the same pool. The fact that you're asking about a Linux-specific function makes it a safe assumption for this particular context.

Others have suggested that you define a custom allocator class. That will only work if you pass that allocator as a template parameter in every STL container declaration you have. That is fairly error-prone, so I wouldn't recommend it as an easy analysis strategy.

As for the second question, no, other things besides STL containers will allocate memory. For example, fstream creates an I/O buffer in an implementation-defined way. Some functions in algorithm create temporary_buffer objects, which does not use an allocator. ( temporary_buffer is not part of the standard; it is part of the original HP/SGI STL though. Other implementations may call it something else, but in any case some functions are allowed to allocate temporary workspace.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM