| 1 | /***************************************************************************** |
| 2 | |
| 3 | Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved. |
| 4 | Copyright (c) 2017, 2018, MariaDB Corporation. |
| 5 | |
| 6 | This program is free software; you can redistribute it and/or modify it under |
| 7 | the terms of the GNU General Public License as published by the Free Software |
| 8 | Foundation; version 2 of the License. |
| 9 | |
| 10 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 13 | |
| 14 | You should have received a copy of the GNU General Public License along with |
| 15 | this program; if not, write to the Free Software Foundation, Inc., |
| 16 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA |
| 17 | |
| 18 | *****************************************************************************/ |
| 19 | |
| 20 | /**************************************************//** |
| 21 | @file include/mem0mem.h |
| 22 | The memory management |
| 23 | |
| 24 | Created 6/9/1994 Heikki Tuuri |
| 25 | *******************************************************/ |
| 26 | |
| 27 | #ifndef mem0mem_h |
| 28 | #define mem0mem_h |
| 29 | |
| 30 | #include "univ.i" |
| 31 | #include "ut0mem.h" |
| 32 | #include "ut0byte.h" |
| 33 | #include "ut0rnd.h" |
| 34 | #include "mach0data.h" |
| 35 | |
| 36 | #include <memory> |
| 37 | |
| 38 | /* -------------------- MEMORY HEAPS ----------------------------- */ |
| 39 | |
| 40 | /** A block of a memory heap consists of the info structure |
| 41 | followed by an area of memory */ |
| 42 | typedef struct mem_block_info_t mem_block_t; |
| 43 | |
| 44 | /** A memory heap is a nonempty linear list of memory blocks */ |
| 45 | typedef mem_block_t mem_heap_t; |
| 46 | |
| 47 | /** Types of allocation for memory heaps: DYNAMIC means allocation from the |
| 48 | dynamic memory pool of the C compiler, BUFFER means allocation from the |
| 49 | buffer pool; the latter method is used for very big heaps */ |
| 50 | |
| 51 | #define MEM_HEAP_DYNAMIC 0 /* the most common type */ |
| 52 | #define MEM_HEAP_BUFFER 1 |
| 53 | #define MEM_HEAP_BTR_SEARCH 2 /* this flag can optionally be |
| 54 | ORed to MEM_HEAP_BUFFER, in which |
| 55 | case heap->free_block is used in |
| 56 | some cases for memory allocations, |
| 57 | and if it's NULL, the memory |
| 58 | allocation functions can return |
| 59 | NULL. */ |
| 60 | |
| 61 | /** Different type of heaps in terms of which datastructure is using them */ |
| 62 | #define MEM_HEAP_FOR_BTR_SEARCH (MEM_HEAP_BTR_SEARCH | MEM_HEAP_BUFFER) |
| 63 | #define MEM_HEAP_FOR_PAGE_HASH (MEM_HEAP_DYNAMIC) |
| 64 | #define MEM_HEAP_FOR_RECV_SYS (MEM_HEAP_BUFFER) |
| 65 | #define MEM_HEAP_FOR_LOCK_HEAP (MEM_HEAP_BUFFER) |
| 66 | |
| 67 | /** The following start size is used for the first block in the memory heap if |
| 68 | the size is not specified, i.e., 0 is given as the parameter in the call of |
| 69 | create. The standard size is the maximum (payload) size of the blocks used for |
| 70 | allocations of small buffers. */ |
| 71 | |
| 72 | #define MEM_BLOCK_START_SIZE 64 |
| 73 | #define MEM_BLOCK_STANDARD_SIZE \ |
| 74 | (srv_page_size >= 16384 ? 8000 : MEM_MAX_ALLOC_IN_BUF) |
| 75 | |
| 76 | /** If a memory heap is allowed to grow into the buffer pool, the following |
| 77 | is the maximum size for a single allocated buffer: */ |
| 78 | #define MEM_MAX_ALLOC_IN_BUF (srv_page_size - 200) |
| 79 | |
| 80 | /** Space needed when allocating for a user a field of length N. |
| 81 | The space is allocated only in multiples of UNIV_MEM_ALIGNMENT. */ |
| 82 | #define MEM_SPACE_NEEDED(N) ut_calc_align((N), UNIV_MEM_ALIGNMENT) |
| 83 | |
| 84 | #ifdef UNIV_DEBUG |
| 85 | /** Macro for memory heap creation. |
| 86 | @param[in] size Desired start block size. */ |
| 87 | # define mem_heap_create(size) \ |
| 88 | mem_heap_create_func((size), __FILE__, __LINE__, MEM_HEAP_DYNAMIC) |
| 89 | |
| 90 | /** Macro for memory heap creation. |
| 91 | @param[in] size Desired start block size. |
| 92 | @param[in] type Heap type */ |
| 93 | # define mem_heap_create_typed(size, type) \ |
| 94 | mem_heap_create_func((size), __FILE__, __LINE__, (type)) |
| 95 | |
| 96 | #else /* UNIV_DEBUG */ |
| 97 | /** Macro for memory heap creation. |
| 98 | @param[in] size Desired start block size. */ |
| 99 | # define mem_heap_create(size) mem_heap_create_func((size), MEM_HEAP_DYNAMIC) |
| 100 | |
| 101 | /** Macro for memory heap creation. |
| 102 | @param[in] size Desired start block size. |
| 103 | @param[in] type Heap type */ |
| 104 | # define mem_heap_create_typed(size, type) \ |
| 105 | mem_heap_create_func((size), (type)) |
| 106 | |
| 107 | #endif /* UNIV_DEBUG */ |
| 108 | |
| 109 | /** Creates a memory heap. |
| 110 | NOTE: Use the corresponding macros instead of this function. |
| 111 | A single user buffer of 'size' will fit in the block. |
| 112 | 0 creates a default size block. |
| 113 | @param[in] size Desired start block size. |
| 114 | @param[in] file_name File name where created |
| 115 | @param[in] line Line where created |
| 116 | @param[in] type Heap type |
| 117 | @return own: memory heap, NULL if did not succeed (only possible for |
| 118 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 119 | UNIV_INLINE |
| 120 | mem_heap_t* |
| 121 | mem_heap_create_func( |
| 122 | ulint size, |
| 123 | #ifdef UNIV_DEBUG |
| 124 | const char* file_name, |
| 125 | unsigned line, |
| 126 | #endif /* UNIV_DEBUG */ |
| 127 | ulint type); |
| 128 | |
| 129 | /** Frees the space occupied by a memory heap. |
| 130 | NOTE: Use the corresponding macro instead of this function. |
| 131 | @param[in] heap Heap to be freed */ |
| 132 | UNIV_INLINE |
| 133 | void |
| 134 | mem_heap_free( |
| 135 | mem_heap_t* heap); |
| 136 | |
| 137 | /** Allocates and zero-fills n bytes of memory from a memory heap. |
| 138 | @param[in] heap memory heap |
| 139 | @param[in] n number of bytes; if the heap is allowed to grow into |
| 140 | the buffer pool, this must be <= MEM_MAX_ALLOC_IN_BUF |
| 141 | @return allocated, zero-filled storage */ |
| 142 | UNIV_INLINE |
| 143 | void* |
| 144 | mem_heap_zalloc( |
| 145 | mem_heap_t* heap, |
| 146 | ulint n); |
| 147 | |
| 148 | /** Allocates n bytes of memory from a memory heap. |
| 149 | @param[in] heap memory heap |
| 150 | @param[in] n number of bytes; if the heap is allowed to grow into |
| 151 | the buffer pool, this must be <= MEM_MAX_ALLOC_IN_BUF |
| 152 | @return allocated storage, NULL if did not succeed (only possible for |
| 153 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 154 | UNIV_INLINE |
| 155 | void* |
| 156 | mem_heap_alloc( |
| 157 | mem_heap_t* heap, |
| 158 | ulint n); |
| 159 | |
| 160 | /** Returns a pointer to the heap top. |
| 161 | @param[in] heap memory heap |
| 162 | @return pointer to the heap top */ |
| 163 | UNIV_INLINE |
| 164 | byte* |
| 165 | mem_heap_get_heap_top( |
| 166 | mem_heap_t* heap); |
| 167 | |
| 168 | /** Frees the space in a memory heap exceeding the pointer given. |
| 169 | The pointer must have been acquired from mem_heap_get_heap_top. |
| 170 | The first memory block of the heap is not freed. |
| 171 | @param[in] heap heap from which to free |
| 172 | @param[in] old_top pointer to old top of heap */ |
| 173 | UNIV_INLINE |
| 174 | void |
| 175 | mem_heap_free_heap_top( |
| 176 | mem_heap_t* heap, |
| 177 | byte* old_top); |
| 178 | |
| 179 | /** Empties a memory heap. |
| 180 | The first memory block of the heap is not freed. |
| 181 | @param[in] heap heap to empty */ |
| 182 | UNIV_INLINE |
| 183 | void |
| 184 | mem_heap_empty( |
| 185 | mem_heap_t* heap); |
| 186 | |
| 187 | /** Returns a pointer to the topmost element in a memory heap. |
| 188 | The size of the element must be given. |
| 189 | @param[in] heap memory heap |
| 190 | @param[in] n size of the topmost element |
| 191 | @return pointer to the topmost element */ |
| 192 | UNIV_INLINE |
| 193 | void* |
| 194 | mem_heap_get_top( |
| 195 | mem_heap_t* heap, |
| 196 | ulint n); |
| 197 | |
| 198 | /** Checks if a given chunk of memory is the topmost element stored in the |
| 199 | heap. If this is the case, then calling mem_heap_free_top() would free |
| 200 | that element from the heap. |
| 201 | @param[in] heap memory heap |
| 202 | @param[in] buf presumed topmost element |
| 203 | @param[in] buf_sz size of buf in bytes |
| 204 | @return true if topmost */ |
| 205 | UNIV_INLINE |
| 206 | bool |
| 207 | mem_heap_is_top( |
| 208 | mem_heap_t* heap, |
| 209 | const void* buf, |
| 210 | ulint buf_sz) |
| 211 | MY_ATTRIBUTE((warn_unused_result)); |
| 212 | |
| 213 | /*****************************************************************//** |
| 214 | Allocate a new chunk of memory from a memory heap, possibly discarding |
| 215 | the topmost element. If the memory chunk specified with (top, top_sz) |
| 216 | is the topmost element, then it will be discarded, otherwise it will |
| 217 | be left untouched and this function will be equivallent to |
| 218 | mem_heap_alloc(). |
| 219 | @return allocated storage, NULL if did not succeed (only possible for |
| 220 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 221 | UNIV_INLINE |
| 222 | void* |
| 223 | mem_heap_replace( |
| 224 | /*=============*/ |
| 225 | mem_heap_t* heap, /*!< in/out: memory heap */ |
| 226 | const void* top, /*!< in: chunk to discard if possible */ |
| 227 | ulint top_sz, /*!< in: size of top in bytes */ |
| 228 | ulint new_sz);/*!< in: desired size of the new chunk */ |
| 229 | /*****************************************************************//** |
| 230 | Allocate a new chunk of memory from a memory heap, possibly discarding |
| 231 | the topmost element and then copy the specified data to it. If the memory |
| 232 | chunk specified with (top, top_sz) is the topmost element, then it will be |
| 233 | discarded, otherwise it will be left untouched and this function will be |
| 234 | equivallent to mem_heap_dup(). |
| 235 | @return allocated storage, NULL if did not succeed (only possible for |
| 236 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 237 | UNIV_INLINE |
| 238 | void* |
| 239 | mem_heap_dup_replace( |
| 240 | /*=================*/ |
| 241 | mem_heap_t* heap, /*!< in/out: memory heap */ |
| 242 | const void* top, /*!< in: chunk to discard if possible */ |
| 243 | ulint top_sz, /*!< in: size of top in bytes */ |
| 244 | const void* data, /*!< in: new data to duplicate */ |
| 245 | ulint data_sz);/*!< in: size of data in bytes */ |
| 246 | /*****************************************************************//** |
| 247 | Allocate a new chunk of memory from a memory heap, possibly discarding |
| 248 | the topmost element and then copy the specified string to it. If the memory |
| 249 | chunk specified with (top, top_sz) is the topmost element, then it will be |
| 250 | discarded, otherwise it will be left untouched and this function will be |
| 251 | equivallent to mem_heap_strdup(). |
| 252 | @return allocated string, NULL if did not succeed (only possible for |
| 253 | MEM_HEAP_BTR_SEARCH type heaps) */ |
| 254 | UNIV_INLINE |
| 255 | char* |
| 256 | mem_heap_strdup_replace( |
| 257 | /*====================*/ |
| 258 | mem_heap_t* heap, /*!< in/out: memory heap */ |
| 259 | const void* top, /*!< in: chunk to discard if possible */ |
| 260 | ulint top_sz, /*!< in: size of top in bytes */ |
| 261 | const char* str); /*!< in: new data to duplicate */ |
| 262 | /*****************************************************************//** |
| 263 | Frees the topmost element in a memory heap. |
| 264 | The size of the element must be given. */ |
| 265 | UNIV_INLINE |
| 266 | void |
| 267 | mem_heap_free_top( |
| 268 | /*==============*/ |
| 269 | mem_heap_t* heap, /*!< in: memory heap */ |
| 270 | ulint n); /*!< in: size of the topmost element */ |
| 271 | /*****************************************************************//** |
| 272 | Returns the space in bytes occupied by a memory heap. */ |
| 273 | UNIV_INLINE |
| 274 | ulint |
| 275 | mem_heap_get_size( |
| 276 | /*==============*/ |
| 277 | mem_heap_t* heap); /*!< in: heap */ |
| 278 | |
| 279 | /**********************************************************************//** |
| 280 | Duplicates a NUL-terminated string. |
| 281 | @return own: a copy of the string, must be deallocated with ut_free */ |
| 282 | UNIV_INLINE |
| 283 | char* |
| 284 | mem_strdup( |
| 285 | /*=======*/ |
| 286 | const char* str); /*!< in: string to be copied */ |
| 287 | /**********************************************************************//** |
| 288 | Makes a NUL-terminated copy of a nonterminated string. |
| 289 | @return own: a copy of the string, must be deallocated with ut_free */ |
| 290 | UNIV_INLINE |
| 291 | char* |
| 292 | mem_strdupl( |
| 293 | /*========*/ |
| 294 | const char* str, /*!< in: string to be copied */ |
| 295 | ulint len); /*!< in: length of str, in bytes */ |
| 296 | |
| 297 | /** Duplicate a block of data, allocated from a memory heap. |
| 298 | @param[in] heap memory heap where string is allocated |
| 299 | @param[in] data block of data to be copied |
| 300 | @param[in] len length of data, in bytes |
| 301 | @return own: a copy of data */ |
| 302 | inline |
| 303 | void* |
| 304 | mem_heap_dup(mem_heap_t* heap, const void* data, size_t len) |
| 305 | { |
| 306 | return(memcpy(mem_heap_alloc(heap, len), data, len)); |
| 307 | } |
| 308 | |
| 309 | /** Duplicate a NUL-terminated string, allocated from a memory heap. |
| 310 | @param[in] heap memory heap where string is allocated |
| 311 | @param[in] str string to be copied |
| 312 | @return own: a copy of the string */ |
| 313 | inline |
| 314 | char* |
| 315 | mem_heap_strdup(mem_heap_t* heap, const char* str) |
| 316 | { |
| 317 | return(static_cast<char*>(mem_heap_dup(heap, str, strlen(str) + 1))); |
| 318 | } |
| 319 | |
| 320 | /** Duplicate a string, allocated from a memory heap. |
| 321 | @param[in] heap memory heap where string is allocated |
| 322 | @param[in] str string to be copied |
| 323 | @param[in] len length of str, in bytes |
| 324 | @return own: a NUL-terminated copy of str */ |
| 325 | inline |
| 326 | char* |
| 327 | mem_heap_strdupl(mem_heap_t* heap, const char* str, size_t len) |
| 328 | { |
| 329 | char* s = static_cast<char*>(mem_heap_alloc(heap, len + 1)); |
| 330 | s[len] = 0; |
| 331 | return(static_cast<char*>(memcpy(s, str, len))); |
| 332 | } |
| 333 | |
| 334 | /**********************************************************************//** |
| 335 | Concatenate two strings and return the result, using a memory heap. |
| 336 | @return own: the result */ |
| 337 | char* |
| 338 | mem_heap_strcat( |
| 339 | /*============*/ |
| 340 | mem_heap_t* heap, /*!< in: memory heap where string is allocated */ |
| 341 | const char* s1, /*!< in: string 1 */ |
| 342 | const char* s2); /*!< in: string 2 */ |
| 343 | |
| 344 | /****************************************************************//** |
| 345 | A simple sprintf replacement that dynamically allocates the space for the |
| 346 | formatted string from the given heap. This supports a very limited set of |
| 347 | the printf syntax: types 's' and 'u' and length modifier 'l' (which is |
| 348 | required for the 'u' type). |
| 349 | @return heap-allocated formatted string */ |
| 350 | char* |
| 351 | mem_heap_printf( |
| 352 | /*============*/ |
| 353 | mem_heap_t* heap, /*!< in: memory heap */ |
| 354 | const char* format, /*!< in: format string */ |
| 355 | ...) MY_ATTRIBUTE ((format (printf, 2, 3))); |
| 356 | |
| 357 | /** Checks that an object is a memory heap (or a block of it) |
| 358 | @param[in] heap Memory heap to check */ |
| 359 | UNIV_INLINE |
| 360 | void |
| 361 | mem_block_validate( |
| 362 | const mem_heap_t* heap); |
| 363 | |
| 364 | #ifdef UNIV_DEBUG |
| 365 | /** Validates the contents of a memory heap. |
| 366 | Asserts that the memory heap is consistent |
| 367 | @param[in] heap Memory heap to validate */ |
| 368 | void |
| 369 | mem_heap_validate( |
| 370 | const mem_heap_t* heap); |
| 371 | |
| 372 | #endif /* UNIV_DEBUG */ |
| 373 | |
| 374 | /*#######################################################################*/ |
| 375 | |
| 376 | /** The info structure stored at the beginning of a heap block */ |
| 377 | struct mem_block_info_t { |
| 378 | ulint magic_n;/* magic number for debugging */ |
| 379 | #ifdef UNIV_DEBUG |
| 380 | char file_name[8];/* file name where the mem heap was created */ |
| 381 | unsigned line; /*!< line number where the mem heap was created */ |
| 382 | #endif /* UNIV_DEBUG */ |
| 383 | UT_LIST_BASE_NODE_T(mem_block_t) base; /* In the first block in the |
| 384 | the list this is the base node of the list of blocks; |
| 385 | in subsequent blocks this is undefined */ |
| 386 | UT_LIST_NODE_T(mem_block_t) list; /* This contains pointers to next |
| 387 | and prev in the list. The first block allocated |
| 388 | to the heap is also the first block in this list, |
| 389 | though it also contains the base node of the list. */ |
| 390 | ulint len; /*!< physical length of this block in bytes */ |
| 391 | ulint total_size; /*!< physical length in bytes of all blocks |
| 392 | in the heap. This is defined only in the base |
| 393 | node and is set to ULINT_UNDEFINED in others. */ |
| 394 | ulint type; /*!< type of heap: MEM_HEAP_DYNAMIC, or |
| 395 | MEM_HEAP_BUF possibly ORed to MEM_HEAP_BTR_SEARCH */ |
| 396 | ulint free; /*!< offset in bytes of the first free position for |
| 397 | user data in the block */ |
| 398 | ulint start; /*!< the value of the struct field 'free' at the |
| 399 | creation of the block */ |
| 400 | |
| 401 | void* free_block; |
| 402 | /* if the MEM_HEAP_BTR_SEARCH bit is set in type, |
| 403 | and this is the heap root, this can contain an |
| 404 | allocated buffer frame, which can be appended as a |
| 405 | free block to the heap, if we need more space; |
| 406 | otherwise, this is NULL */ |
| 407 | void* buf_block; |
| 408 | /* if this block has been allocated from the buffer |
| 409 | pool, this contains the buf_block_t handle; |
| 410 | otherwise, this is NULL */ |
| 411 | }; |
| 412 | |
| 413 | #define MEM_BLOCK_MAGIC_N 764741555 |
| 414 | #define MEM_FREED_BLOCK_MAGIC_N 547711122 |
| 415 | |
| 416 | /* Header size for a memory heap block */ |
| 417 | #define ut_calc_align(sizeof(mem_block_info_t),\ |
| 418 | UNIV_MEM_ALIGNMENT) |
| 419 | |
| 420 | #include "mem0mem.ic" |
| 421 | |
| 422 | /** A C++ wrapper class to the mem_heap_t routines, so that it can be used |
| 423 | as an STL allocator */ |
| 424 | template<typename T> |
| 425 | class mem_heap_allocator |
| 426 | { |
| 427 | public: |
| 428 | typedef T value_type; |
| 429 | typedef size_t size_type; |
| 430 | typedef ptrdiff_t difference_type; |
| 431 | typedef T* pointer; |
| 432 | typedef const T* const_pointer; |
| 433 | typedef T& reference; |
| 434 | typedef const T& const_reference; |
| 435 | |
| 436 | mem_heap_allocator(mem_heap_t* heap) : m_heap(heap) { } |
| 437 | |
| 438 | mem_heap_allocator(const mem_heap_allocator& other) |
| 439 | : |
| 440 | m_heap(other.m_heap) |
| 441 | { |
| 442 | // Do nothing |
| 443 | } |
| 444 | |
| 445 | template <typename U> |
| 446 | mem_heap_allocator (const mem_heap_allocator<U>& other) |
| 447 | : |
| 448 | m_heap(other.m_heap) |
| 449 | { |
| 450 | // Do nothing |
| 451 | } |
| 452 | |
| 453 | ~mem_heap_allocator() { m_heap = 0; } |
| 454 | |
| 455 | size_type max_size() const |
| 456 | { |
| 457 | return(ULONG_MAX / sizeof(T)); |
| 458 | } |
| 459 | |
| 460 | /** This function returns a pointer to the first element of a newly |
| 461 | allocated array large enough to contain n objects of type T; only the |
| 462 | memory is allocated, and the objects are not constructed. Moreover, |
| 463 | an optional pointer argument (that points to an object already |
| 464 | allocated by mem_heap_allocator) can be used as a hint to the |
| 465 | implementation about where the new memory should be allocated in |
| 466 | order to improve locality. */ |
| 467 | pointer allocate(size_type n) |
| 468 | { |
| 469 | return(reinterpret_cast<pointer>( |
| 470 | mem_heap_alloc(m_heap, n * sizeof(T)))); |
| 471 | } |
| 472 | pointer allocate(size_type n, const_pointer) { return allocate(n); } |
| 473 | |
| 474 | void deallocate(pointer, size_type) {} |
| 475 | |
| 476 | pointer address (reference r) const { return(&r); } |
| 477 | |
| 478 | const_pointer address (const_reference r) const { return(&r); } |
| 479 | |
| 480 | void construct(pointer p, const_reference t) |
| 481 | { |
| 482 | new (reinterpret_cast<void*>(p)) T(t); |
| 483 | } |
| 484 | |
| 485 | void destroy(pointer p) |
| 486 | { |
| 487 | (reinterpret_cast<T*>(p))->~T(); |
| 488 | } |
| 489 | |
| 490 | /** Allocators are required to supply the below template class member |
| 491 | which enables the possibility of obtaining a related allocator, |
| 492 | parametrized in terms of a different type. For example, given an |
| 493 | allocator type IntAllocator for objects of type int, a related |
| 494 | allocator type for objects of type long could be obtained using |
| 495 | IntAllocator::rebind<long>::other */ |
| 496 | template <typename U> |
| 497 | struct rebind |
| 498 | { |
| 499 | typedef mem_heap_allocator<U> other; |
| 500 | }; |
| 501 | |
| 502 | private: |
| 503 | mem_heap_t* m_heap; |
| 504 | template <typename U> friend class mem_heap_allocator; |
| 505 | }; |
| 506 | |
| 507 | template <class T> |
| 508 | bool operator== (const mem_heap_allocator<T>& left, |
| 509 | const mem_heap_allocator<T>& right) |
| 510 | { |
| 511 | return(left.heap == right.heap); |
| 512 | } |
| 513 | |
| 514 | template <class T> |
| 515 | bool operator!= (const mem_heap_allocator<T>& left, |
| 516 | const mem_heap_allocator<T>& right) |
| 517 | { |
| 518 | return(left.heap != right.heap); |
| 519 | } |
| 520 | |
| 521 | #endif |
| 522 | |