Simple Definitions:
Handle: A handle is a "pointer to a pointer to something in memory." Or, at least that's what a long-haul trucker librarian once told me over a CB radio.
In C, writing
*Handle dereferences a handle to get a Pointer
C code example:
myCMemPtr = *myCMemHdl; // dereference a CMemHdl to get a CMemPtr
...or to get all the way from a handle to an element of a structure:
surprise = (*myCMemHdl)->monkeySurprise; // the (* ) dereferences from the handle to the pointer and the -> dereferences from the pointer to the intended field
-- or --
surprise = (**myCMemHdl).monkeySurprise; // the ** dereferences from the handle to the structure and the . indexes to the intended field
n.b. You have to enclose any dereference using * with () so that the compiler knows to fully evaluate that expression before attempting to dereference to "->" or access "." the intended field/member in a data structure. (), -> and . are operators with the highest priority, and * is 1 step lower. So, if you didn't use (), there would be a compiler error.
For more info, see: Operator Precedence in C
Pointer: points to memory. Hey! Look at that! Well...huh! Who knew that monkeys could juggle flaming umbrellas! You don't see that every day! I'm glad I saw the ad!
In C, writing
Ptr->someContainer dereferences a pointer to directly access a container in memory.
C code example:
myCMemPtr->monkeySurprise = 5000; // juggling monkeys is a surprise of high magnitude
(Similarly, for the sake of repetition, handles are merely a
specific kind of pointer to a container that holds a pointer.)
Memory: A place to hold anything and everything a program needs to do its work
(
Memory Warning: For software engineers,
Memory, from the musical CATS, is the saddest of all songs. It represents how engineers feel as their lives and relationships begin slipping away after too much coffee and extreme, programming-related sleep deprivation. Some hapless souls may even turn into dreaded Code Vampires, also known as the Programming Undead. So, it's probably safest not to become a software engineer or listen to anything from CATS.)
C code examples:
myCMemory.monkeySurprise = 5000; // Note: when you are accessing a structure directly (no pointer or handle), you use a "." to get to the field.
Getting an effective address (pointer) to the structure or an element of a structure -- often when defined as a local variable in a routine:
myCMemPtr = &myCMemory; // get the effective address, a pointer, to the CMemory structure
myMonkeyPtr = &myCMemory.monkeySurprise; // get the effective address of the monkeySurprise field within the CMemory structure
As in my previous post, in C, you need to take the
effective address (a pointer) of a structure/stack element when you want to pass it as a variable to another function.
Finally, in C, here is how you would define a memory structure in a .h file, along with its related pointer and handle.
Code:
struct Name { // the main structure definition with all of its fields
varType varName1;
varType2 varName2;
etc.
}
typedef struct Name Name; // a typedef for the name of a struct
typedef Name *Pointer; // define a pointer that points to it
typedef Pointer *Handle; // define a handle that points to the pointer
...or to create a formal example from all of the information above:
Code:
struct CMemory {
int numCats;
int catCostumeType;
int maxCoffee;
int vampireTears;
short monkeySurprise;
Handle umbrellaHandle;
Str255 codeZombiePSA;
};
typedef struct CMemory CMemory;
typedef CMemory *CMemPtr;
typedef CMemPtr *CMemHdl;
(n.b. full type definitions for structures and their pointers and handles can help prevent many accidental errors/misuse. Also, to evaluate the size of a variable or structure, you can use the macro
sizeof(CMemory), for example.
Code:
#define kCMemSize sizeof(CMemory) // create a constant for the size of a CMemory structure
You never have to count type sizes or bytes, just include the
sizeof() macro in your code or a #define and the compiler will do it for you!
Why so much indirection?
Considering
Handle->Pointer->MemoryContainer, the reason there is an extra layer of indirection is that handles are a
friendly and
consistent way for user software to keep track of dynamic structures in memory. Then, at the pointer level, the MacOS/System can manipulate pointers and blocks of memory to optimize memory handling (because it is very sneaky, and the task is complicated and much bigger than the scope of individual applications) without causing any problems at the user program level. If the system has to move something around in memory, it moves it and then tells the handle where it is supposed to point to find the container (or droids) it is looking for. And when an application dereferences from the handle to the new pointer, everything works -- like magic!
So...to repeat again, very generally:
HANDLE (flexible program reference) -> POINTER (direct access/system maintenance) -> MEMORY LOCATION (data structures, resources, etc.)
I'm Still Confused and Need a Bedtime Story:
"A Boy and His Socks"
(The best bedtime story ever by !Aesop)
Once upon a time, a boy named Rastenbury Phipps knew that he owned many socks, and he loved the synthetic feel of polyester. "How wonderful that I own so many polyester socks!" he exclaimed. However, his mother, Soxy Phipps, kept moving the socks around because she was an obsessive-compulsive sock-sorting fanatic who arranged the socks inside a giant, highly-organized Polyester Pagoda.
"She's definitely crazy," thought Rastenbury, while on his daily sock shopping and appreciation trip to Sock Emporium. Sometimes, on rainy days when Soxy was very bored, she would move the socks around multiple times and try to make them fit in the smallest possible space so that she had room for more socks! "What a fantastic way to spend my time," she mentioned to a pair of sassy, multi-colored argyles in drawer #3480224.
When Rastenbury wanted specific socks, all he had to do was ask Soxy where they were and she would immediately fetch them for him from the correct drawer in the Polyester Pagoda. She had eidetic sock memory and knew the location of every single pair! How miraculous!
One day, as Rastenbury absent-mindedly consumed warm milk and cookies, he concluded that it was enough just to remember that he had many different kinds of socks and that he knew how to find them. He trusted his mother and didn't
ALWAYS need to know exactly where they were
UNLESS he wanted to wear or admire them. In fact, sometimes, Rastenbury would look into the large hall mirror and quietly whisper "I am a sock admirer" and then look furtively to the left and right to make sure that nobody heard him. Apparently, the crazy apple didn't fall far from the applesock tree.
Regardless, until Rastenbury needed his socks and asked for a specific pair, his mother, Soxy, could sort them to her heart's content, and everyone lived happily ever after without ever experiencing sock errors or argyle aggression.
The Intentionally Repetitive Moral of the Story:
HANDLE Analogy: Rastenbury knows that he has socks (somewhere) and knows how to find them -- by asking his mother, Soxy, for them
POINTER Analogy: Soxy knows
exactly where the socks are, because she has placed them in specific drawers in the Polyester Pagoda
MEMORY CONTAINER Analogy: The specific drawer location in the Polyester Pagoda where Soxy has stored the socks
The End.
Advanced Memory End-Note:
If you are not afraid of additional toolbox technical detail, read the following section:
You might wonder why you can't just skip the handle dereference and use the pointer directly, and the answer is that you can...and may need or want to. So, in order to do that, you tell the system to move the chunk of memory high and out of the way, then you lock it, then you get the pointer, then you use it for whatever you want, and when you are done, you unlock it again so the system can go back to moving it around, as needed. The reason you have to lock it is that if the system happened to move the memory and its pointer out from under you while you were trying to use it, you would be using an old reference and either crash or trash memory. So, don't do that.
Or, in other words:
Code:
h = GetResource(rType,rID); // get some resource
MoveHHi(h); // move it high in the application (or system) heap
HLock(h); // lock it so the system knows not to try to relocate it
p = *h; / get the pointer to the resource
(do some pointer stuff that is safe because the memory is locked)
HUnlock(h); // unlock it when you are done, so the system knows that it is safe to move it around again
The reason you just don't lock a handle without first calling MoveHHi() is that you may cause heap fragmentation by locking a chunk of memory that prevents the system from using it as a large, contiguous block. MoveHHi() moves the related chunk as high as it can and out of the way.
Other Routines:
CompactMem() does a zone cleanup and compaction, purging all purgeable stuff and ReserveMem() reserves memory low in the current heap. Alternately, ReserveMemSys() reserves memory in the System Heap. Finally, FreeMem() checks the amount of free memory available and MaxMem() lets you maximize a zone and check how much memory is available -- you can look up all these routines in ThinkReference. The goal is always to maintain the largest contiguous block of memory, make sure there is enough memory before trying to do things, avoid fragmenting memory by locking things in the middle of the heap, and not creating "memory leaks" by allocating things and then not disposing of them when you are done. There are numerous ways to make your app very well-behaved with respect to memory allocation (NewPtr, NewHandle, etc.) using these routines, but it's something to continue to research for later. And, when allocating memory, the sizeof() macro is very handy to evaluate the size of a specific structures, as above.
Heap or Stack:
Heap:
When you explicitly allocate memory - via NewPtr, etc. - or load a resource or create a NewHandle, etc., it goes into the current
HEAP. The heap is a large area of memory used by an application, or the system. It starts at the bottom of the related allocated address space and grows
UPWARDS. For applications, the System creates it based on the SIZE resource info, visible in the Finder as min size, max size and preferred size.
Stack:
Conversely, when you define a local variable in an application routine, such as a structure or loop counter, it goes on the
STACK. The stack starts at the top of the allocated address space and grows
DOWNWARDS. When you pass local variables from routine to routine, all the pointers/memory references are relative to the current stack pointer. Because the stack grows downwards, everything you passed on the stack is a higher (or positive) relative reference and the calling parameters for each routine exist in a structure known as a StackFrame (for later discussion).
You never want to run out of space, take up too much space (that you don't really need), or have a Heap/Stack collision, where you allocate or use up all available memory (unbounded/excessive recursion, declaring large structures as local variables, etc.), and the stack and the heap run into each other (or you run out of stack space). So, code defensively and always make sure you have enough space, rather than just assuming that you do!
Code:
Example Application Memory Zone
=================
STACK
=================
|
v
...and never the twain shall meet! ;)
ʌ
|
=================
HEAP
=================