Does anyone have a link to a very good memory manager tutorial?
Printable View
Does anyone have a link to a very good memory manager tutorial?
memory management comes in all sorts of guises at all sorts of levels. Explain a little more about the sort of memory management you want to do.
Actually, memory management is very simple, from the O/S perspective. Memory Management has one and only one purpose--
To fulfill memory requests and keep the heap clean.
---
Let me explain a little bit about how memory managers actually work (something Microsoft has not yet implimented by the way, even in XP)...
---
First, when an application runs, the O/S allocates a 'heap' of RAM of the size specified by the compiler when the executable was built. This is an adjustable field, but many times is defaulted to something like 384K.
Then, it loads the application to that 0-base address (relative to the heap) and sets the application stack pointer to start running there. The application's stub resolves the jumptables, links, and static/global variable spaces, establishes a stackframe, sets up master pointers and then jumps to main().
---
Now, once an application is up and running, a good memory manager will then fulfill any RAM requests (eg. 'malloc()', etc.), so long as
a) there is enough free RAM available in the Application Heap, or
b) there is enough free RAM available in the System Heap
In either case, the memory should be consecutive. Free memory should related to 'largest free block'... Microsoft, however just builds an array of pointers to free blocks of any size. So for example, if you needed a block 100K in size, Microsoft might actually allocate 20 different blocks with 20 pointers, each block being 5K in size. In essense you would be working with a 'super-block'. To the application it thinks it's getting a 100K block- but in reality performance is slowed because the memory manager must traverse 20 different blocks transparently to the application. This creates bad overhead process hits because the O/S isn't written correctly.
A properly written memory manager would always be looking to move blocks around in RAM (heap compaction), and keep wide-open spaces, wide-open.
In any event, the block is allocated.
RAM blocks usually have a 12-byte header preceding the address of the block you are handed. The memory manager then knows all it needs to know about each RAMblock because it can query this header and learn about the block. Usually the memory manager will have a master list of pointers for all blocks used by the application.
---
Now, if you mean managing your own application memory, it means dynamic allocation, keeping track of and properly deallocating your pointers. Failing gracefully if sufficient RAM can't be allocated for a process, etc.
You might try www.microsoft.com or a bookstore.
speaking of which... under which conditions could malloc/realloc/calloc fail besides inadequate remaining free memory? i'd like to know so i can gauge free memory at the very start, and decide whether or not to bomb with an 'insufficient memory' error at the very beginning... or i could malloc it all at the very beginning, but i may/may not use all that space so it's something i frown upon... any advice? i know you know your stuff Sayeh, so help me out! :) thanks...
In all fairness, I'm not going to give a "definitive" answer on this one, doubleanti, because I can't vouch for how Windows is written. Except that it was poorly done and based on OOPs.
There are many cases where any Xalloc() call will fail and it's because some other portion of the O/S has trashed it (such as winsock scrambling a block header...
All this is to say, that I can't say "Here are the only X ways in which Xalloc() will fail..."
There are just too much bizarre ways in which it can fail-- even if you've done everything right!
yeah, that really stinks that we don't really know who's-on-first, since it could have been rewritten to begin with... that really stinks... and i think that that undermines what it means to be a programmer... to be on-first...
http://www.linuxdoc.org/LDP/tlk/tlk.html, the intel system manual, and http://jan.netcomp.monash.edu.au/OS/l7_1.html
http://www.cs.jhu.edu/~yairamir/cs418/os5/
also looks like it has some good information.
I've noticed that a lot of people do not use new and delete, especially inside of Windows code. Why? All of the source files for OWL in Borland use new and delete and never use the *alloc(s). The same is true for DOS code. Many people just do not use new and delete. My books do differentiate between new and delete and the *alloc(s). Seems to me if you use C++ you would use new and delete and vice versa. I've also heard that the reason is because new and delete do not work well with dynamic memory, but this cannot be true as OWL performs flawlessly using new and delete.
Is this the same reason why people use printf() in C++ rather than cout - just depends on what they are used to using.
For Windows apps I usually allocate from the system using the memory management functions in the API. However, some of that becomes very unstable if a previous app has thrashed the memory by not unlocking or failing to free up a memory handle/pointer.
Been a while since I've done some Windows code so I've forgotten the mem functions in Windows - been doing lots of DJGPP and LFB stuff. Love my DJGPP.
Thanks for the links, everyone. Every post was helpful. To quickly address Bubba's question about new and delete there are a number of reasons why they aren't used as much. For games, it is always better to make a custom memory manager. Don't forget that both new and delete are operators, therefore you can overload them.
Since my current project is very platform dependent, I have found myself open to many options. The manager I wrote actually does not use linked lists, like any other one i have written before now. I found that just using the headers and pointer arithmetic works just fine.
say bubba, sounds like i'm gonna switch to cout and new/delete then! can you give me any resources on the subject? like a complete listing of their usage... [i can sort of grasp them... but not as much as i could a regular function]... i have C:The Complete Reference... which doesn't help too much... [new and delete has a small section tho...] oh, and i learnt C first without caring much for the ++ parts... but now that i use them throughly and on a regular basis, i'd really think it time to switch... PM me... thank you very much...
The syntax for using new and delete is:
For single C data types and user defined types
pointer_var = new var_type;
delete pointer_var;
For arrays of C data types and user defined types
pointer_var = new var_type[arraysize];
delete [size] pointer_var;
I'll use a simple VGA buffer to explain new and delete.
Although I rarely see - delete [size] pointer_var - used, it is correct to specify the size rather than just using delete [] pointer_var. Newer compilers may substitute in the [size] portion for you - older compilers will flag an error if you just use []. The [size] portion informs delete the size of the memory object to delete - It already knows the type so you do not have to type cast for delete. It knows the size of the object, but it does not know how many objects are in the array - This is what the [size] portion tells delete. Remember the memory footprint is numobjects*sizeof(object) not just sizeof(object).Code:unsigned char *Screen=new unsigned char[64000];
delete [64000] Screen; //delete 64000 objects of type
//unsigned char
typedef unsigned char BYTE;
BYTE *Screen=new BYTE[64000];
delete [64000] Screen; //delete 64000 objects of type BYTE
Remember that because of the way that memory allocation is managed with new you can only use delete to free up memory if you used new to allocate it. Very important.
Both allocate memory from the heap, both return null on failure. But, new does a couple more things than malloc() does.
- New automatically computes the size of the type being allocated. You do not have to use the sizeof operator. This prevents the wrong amount of memory from being accidentally allocated.
- New automatically returns the correct pointer type so you don't have to use a type cast.
- It is possible to initialize and object by using new. For instance - int *myint; myint=new int(1000); This will initialize myint with the value of 1000. You cannot initialize arrays with new, however.
- It is possible to overload new and delete relative to a class.
New calls the constructor for the object you wish to create if you are creating a class object. Delete will call the class's destructor. Very nice way to handle memory as it relates to objects and classes. However note that if you have any classes that have virtual functions or are using derived types then you will be using late binding and your program will run a bit slower than if you used early binding. From my book it does not seem to me that using new and delete is any slower than using the *alloc(s). Speed as it relates to objects is based on early and late binding and which one of them you are using. The speed hit is negligible on today's systems so using virtual functions or derived types is not a bad idea. It allows your program greater functionality and allows you to create one interface to do multiple things.
The beauty of new and delete is that you can create arrays of objects and delete them correctly since it already knows the size and type of the object. Notice that I said the size of the object. If you have an array of objects, you should specify the array size or number of elements in the delete operator using delete [size] objectpointer.
This could have just as easily been done if vertex was a class.Code:
struct vertex
{
double x;
double y;
double z;
void Test(void);
};
//Array example
vertex *MyVertexes=new vertex[10];
delete [10] MyVertexes;
//Non-array example
vertex *MySingleVertex=new vertex;
delete MySingleVertex; //not an array of vertexes
Overloading new and delete
To overload new and delete:
void *operator new(size_t size)
{
//allocate here
return pointer_to_mem;
}
void *operator delete(void *mem)
{
//free memory pointed to by *mem
}
size_t is an integer type. These (new and delete) can be overloaded globally or relative to one or more classes. This can become confusing just remember that when you use new and delete with an object that is controlled by a class that has overloaded new and delete, the class's version of new and delete will be used, just like when overloading unary operators.
About cout and other I/O functions
C++'s I/O system is rather large so I could not explain every facet of it here. My book has an entire chapter devoted to it so I'm going to even try to explain it here. Look on some websites about C++ I/O system or get a book by Herbert Schildt or Bjarne Strousop. With C++ I/O system it is possible to stream objects to disk and restore them later - this is helpful in Windows when saving or preserving the current desktop state. It allows you to create a persistent desktop. The I/O system is very powerful and very useful.
AFAIK the C++ standard just requires the use of the [] operator for deletion of arrays and not delete [size]. The system will maintain information about the size of the memory allocated, so it knows how much to delete (as it does using free()) and the operator [] is needed to ensure multiple destructors are called.Quote:
Although I rarely see - delete [size] pointer_var - used, it is correct to specify the size rather than just using delete [] pointer_var. Newer compilers may substitute in the [size] portion for you - older compilers will flag an error if you just use []. The [size] portion informs delete the size of the memory object to delete - It already knows the type so you do not have to type cast for delete.
Also most new and delete implementations seem to call malloc() and free() and so are only really needed for the automatic calling of constructors and destructors.
Yes, new does use malloc but it is eaiser to use new with objects than always allocating numobjects*sizeof(objects). Also like you said it is vital to use new and delete when it comes to classes, ctors, and dtors. If you type in delete [] in older compilers it will say operand expected in .... You must use delete [size] pointer_var or it will not work. My Borland C++ 4.52 does accept delete [] or delete [size].
hey thanks a lot bubba... and zen too! you guys really help me out! thanks...
New is good since it is a default way of allocating memory. You can make your own memory management system and then overload a class to use new (or delete) so that anyone else using your class can adapt to the change without learning any new syntax. New and delete are one of the advantages of the C++ langauge, not using them would be omitting part of the language. For example, I wouldn't say "I refuse to use the - sign for subtraction."
You know, and this is just my humble opinion, but I think it's kind of pathetic to want a statement added to a language because a programmer is too lazy to write either a macro or a function to meet their needs.
You only have to write it once because you can create your own libraries and headers, you know.
I just think it's kind of lazy to expect the world to change around a laziness issue... I also think it introduces yet another layer of obfuscation to new programmers about what's actually going on.
sorry sayeh old chap this is one time when i will disagree with you. new and delete and their counterparts new[] and delete[] were necessary because malloc does not know about calling constructors and free does not know about calling destructors.Also making them operators rather than keywords gives us the possibility of overloading them to do memory management as necessary for your classes.
I have just read all the posts.. It will be very helpfull.
are we allowed to type-cast pointers allocated with new/delete? i'd think so... also, i've noticed that sometimes i have multiple [seperate] allocations within the same scope with multiple pointers. to prevent having to have a complicated freeing scheme if an allocation fails at an intermediate allocation, i've decide to use but one pointer for the allocation/deallocation, and effectively segment it using seperate pointers. so far this seems to work out, since all of the pointers have the same base type [as does the master pointer]. however, if i reassign offset-ed addresses to offset pointers of different base types, [both signedness and data width], will this still work out? since the offsets are relative to the base type of the master pointer, i'll have to assign them such... and afterwards if using the offset pointers, they'll be relative to the base types of those respective pointers, correct? thank you.
New and delete already know the type of the pointer thus eliminating the need or usefulness of typecasting with them. I don't know what new and delete will do if you actually find a way to re-type them via a typecast. Crashing your program is the first thing that comes to mind, especially if you are trying to change the type via a typecast.
If you are returning a pointer to memory then you are using void *. This is what malloc returns, but not new. Even though new does use malloc for allocation it does not return void *. New returns a correctly typed pointer to the newly allocated object. It creates the object or can also instantiate a class. Here it is allocating 4000 signed bytes and returning a correctly typed pointer.
char *Test=new char[4000];
int *Test=new int[4000]
This new returns char * not void *.
Test has been intialized and created. No typecast needed.
In the second one, 4000 integers are allocated - malloc(4000*sizeof(int)); or 8000 bytes.
Invalid:
char *Test=(unsigned int *)new char[4000];
The left side does not match the right side so this will not be accepted by the compiler.
This statement is not valid. You are telling the compiler to assign the character pointer Test to point to a character array (region of memory). However, the typecast is also telling the compiler that Test should be an unsigned int *, but new is saying char. This will flag an error in your compiler.
Here new is instantiating a class which calls MyClass's constructor.
Again, no typecast needed in this use or future uses of Test. It is already known.
MyClass *Test=new MyClass;
Since you use the datatype or class in the new statement, it is not correct to re-typecast the data type to a different type.
If you are wanting a mem management like in Windows then your functions should return void *. Then it is up to the programmer what data type his pointer is and he can do this with typecasting.
Left side matched right side - char FAR *Text=(char FAR *) - void FAR * has been typecasted to char FAR *. Now Text is a signed character pointer.Code:char FAR *Text=0;
HGLOBAL memhandle;
memhandle=GlobalAlloc(GPTR,2048);
Text=(char Far *)GlobalLock(memhandle);
GlobalAlloc will allocate the memory and return the handle to that area. To use it you must call GlobalLock to get a pointer to that area of memory. Here I've typecasted it to be a char FAR pointer -GlobalLock returns void FAR * so I can typecast it to whatever data type I want that memory to be.
You could write similar functions - one that allocs the memory and returns a handle - that is tracked by your mem management system. Then you call another function to lock that area and return a pointer to it. In a non-multi-task environment you need not track which process that mem belongs to, just that it has been locked and allocated and cannot be moved or tampered with. Then you can typecast the void far * or in DJGPP just void * to be whatever data type you need. Cleanup is not as clean since the type is really not known by your functions, just by you. They are merely returning a void * pointer which points to an address unknowing of the data type.
New and delete are much better and it might be beneficial for you to just override new and delete to get them to do exactly what you want. As long as you make destructors for each of your derived classes and for the base class, you should not have any problems with new and delete causing memory leaks.
Delete will only fail when the system has been messed up by a wild pointer or it is used with a pointer that has not been allocated with new or when you try to delete a null pointer - not a good idea
New will only fail if the system has been messed up by a wild pointer or if there is not enough memory for the object.
In protected mode a wild pointer should not mess up your system or frag someone else's code. Your code will surely cease functioning, but everything else *should* be fine.
I'm not sure about resizing memory areas with new - never had the need for it so I've never tried it. Pretty sure that since the object has already been created and init that this is not allowed. You must delete the pointer and realloc with the new size of the object.
Give me a better idea of what you want with this mem management thing and I'll help you code it. We've been through this mem thing a lot and perhaps I'm misunderstanding you - would love to help. Promise that I'll think outside of the box - just this once.
:D
if i used new/delete with this, could i have rgb/div-table have different base types, and be assigned to point to offsets of master_buffer?Code:/***************************** fuzz ********************************************
This function takes coordinate parameters and fuzzes the graphics display. It
his perhaps the slowest of all graphics functions in this program.
*******************************************************************************/
vo OUTPUT :: fuzz (un xl, un yl, un xh, un yh)
{
un *master_buffer;
un *r_add_table,
*g_add_table,
*b_add_table,
*div_table;
un alloc_size = (yh - yl) * (xh - xl);
// allocate the master buffer
if ( (master_buffer = (un *) calloc (alloc_size * 4, 4)) == NULL) return;
r_add_table = master_buffer + 0 * alloc_size;
g_add_table = master_buffer + 1 * alloc_size;
b_add_table = master_buffer + 2 * alloc_size;
div_table = master_buffer + 3 * alloc_size;
// add the buffers
for (un u0 = 0; u0 < yh - yl; u0++)
for (un u1 = 0; u1 < xh - xl; u1++)
{
un pix_add = h.vb.g_pixel (u1 + xl, u0 + yl);
uc r, g, b;
// over line flow, reword...
r = (pix_add >> (h.vb.vars.g_bpp + h.vb.vars.b_bpp)) & h.vb.vars.r_max;
g = (pix_add >> h.vb.vars.b_bpp ) & h.vb.vars.g_max;
b = (pix_add >> 0 ) & h.vb.vars.b_max;
// add the original pixel to the add buffers
*(r_add_table + u0 * (xh - xl) + u1) += r;
*(g_add_table + u0 * (xh - xl) + u1) += g;
*(b_add_table + u0 * (xh - xl) + u1) += b;
*(div_table + u0 * (xh - xl) + u1) += 1;
// add up the rgbs to random adjacent/on pixels
for (un u2 = 0; u2 < 8; u2++)
{
un x_t = u1 + (rand () % 3) - 1,
y_t = u0 + (rand () % 3) - 1;
if ((x_t < (xh - xl)) &&
(y_t < (yh - yl)))
{
*(r_add_table + y_t * (xh - xl) + x_t) += r;
*(g_add_table + y_t * (xh - xl) + x_t) += g;
*(b_add_table + y_t * (xh - xl) + x_t) += b;
*(div_table + y_t * (xh - xl) + x_t) += 1;
}
}
}
for (un u0 = 0; u0 < yh - yl; u0++)
for (un u1 = 0; u1 < xh - xl; u1++)
{
un offset = u0 * (xh - xl) + u1;
*(r_add_table + offset) /= *(div_table + offset);
*(g_add_table + offset) /= *(div_table + offset);
*(b_add_table + offset) /= *(div_table + offset);
}
for (un u0 = 0; u0 < yh - yl; u0++)
for (un u1 = 0; u1 < xh - xl; u1++)
P_PIXEL (xl + u1, yl + u0,
*(r_add_table + u0 * (xh - xl) + u1),
*(g_add_table + u0 * (xh - xl) + u1),
*(b_add_table + u0 * (xh - xl) + u1));
h.vb.update (xl, yl, xh, yh);
free (master_buffer);
}
PS, i thought this gaussian-style blur would be faster then doing it unwound per-pixel, but nope. i guess the same number of calculations are made either way. so, it's asm for me... fortunately i have AoA to read, 1.4k pages... yum... :)
Assembly is very easy, especially if you read AoA. You will be amazed at how much power is packed into so few opcodes. And when you learn it well enough you will be able to do super fast graphics and pretty much super fast anything. Make sure you do a thorough read of it - expecially the chapter on Von Neumann arhitecture and CPU architecture. When you know how it all fits together you can create code that is much faster simply because it is working with the machine instead of against it. Excellent resource that AoA.
Now I see what you are doing. It is a simple bi-linear interpolation only you are doing it a bit different. Actually the bi-linear interpolation will be much faster than doing it the way you have been. No for loops, instead you will be calling your bilinear function for x,y,x+1,y+1 for RGB. This does cause 3 new stack frames to be set up in the calls - make sure your bilinear function is inlined and in pure asm (use the FPU). The inline should help a bit.
also note that my fuzz isn't uniform indirection. [giving it a 'dirty fuzz'] which means my asm arithmetic sequence would be variable. i have some more ideas to try out, so we'll see...
and aside from the speed... again...
Quote:
if i used new/delete with this, could i have rgb/div-table have different base types, and be assigned to point to offsets of master_buffer?