Page 1 of 2

New command "IsMemoryID(*memoryID)

Posted: Fri Feb 09, 2018 8:07 pm
by Kurzer
This may be a stupid suggestion, but I have a use for it.

I miss a IsMemoryID() function to check if a memory ID is still valid.
Unfortunately, it is not possible to use MemorySize(*MemoryID) for this check, because the program will crash if the MemoryID is not valid.

In my case, a procedure allocates memory independently and returns the MemoryID as a result. The calling process must release this memory on its own responsibility.

Since these functions are implemented in an OOP-framework (thanks to mk-soft), I would like to check this allocated memory and release it myself if necessary (because the user forgot to free the memory) when the related object will be destroyed.

Unfortunately this is only possible if I could check an invalid MemoryID without crashing the program.

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 1:27 am
by Dude
kurzer wrote:a IsMemoryID() function to check if a memory ID is still valid
A memory ID is just a pointer to a given address in RAM. It's always going to be valid, even if that memory is freed.

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 1:37 am
by Shield
Dude wrote:
kurzer wrote:a IsMemoryID() function to check if a memory ID is still valid
A memory ID is just a pointer to a given address in RAM. It's always going to be valid, even if that memory is freed.
While this is true that the pointer is still valid (since it is just a regular integer variable), dereferencing (i.e. accessing) the memory certainly isn't.

So @kurzer, memory ownership has always been an issue that has been a big problem in simpler languages such as PB and C because the compiler
doesn't offer any support for it. I'd recommend that you use one of two ways:

1) If you allocate the memory (e.g. in a library you wrote), then you also provide a way to free it.
2) Generally, I prefer it the other way around where the user takes the responsibility and passes a block of memory to your function.

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 2:03 am
by freak
Memory addresses can be re-used for new allocations once they have been freed. So even if the proposed IsMemoryID() function says that the memory is valid, you still don't know whether you can free it because there is no way to find out if it still references the same thing you once allocated. You could be freeing something that is still in use and have a random crash later.

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 5:08 am
by Josh
The safest way is to have only one memory pointer and set it to 0 when you release the memory.

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 10:02 pm
by Kurzer
Shield wrote:1) If you allocate the memory (e. g. in a library you wrote), then you also provide a way to free it.
2) Generally, I prefer it the other way around where the user takes the responsibility and passes a block of memory to your function.
Hello Shield,
1) is the way I am going now. I also offer my own FreeMemory function in my object and store and track the memory address internally to check if the memory has already been released via my function.

2) I can't do it like that, because the user doesn't know how big the memory has to be. This is only decided within the function that allocates the memory. The function may have to perform a ReAllocate() several times. This depends on the data it have to process.
Josh wrote:The safest way is to have only one memory pointer and set it to 0 when you release the memory.
Yes, thats the way I do it now internally in my object, but this does not prevent the user from ignoring my FreeMemory() function and try to release the memory on his own.
freak wrote:Memory addresses can be re-used for new allocations once they have been freed. So even if the proposed IsMemoryID() function says that the memory is valid, you still don't know whether you can free it because there is no way to find out if it still references the same thing you once allocated. You could be freeing something that is still in use and have a random crash later.
@Freak, okay, I understand the dilemma.
However, I wonder why Fred didn't implemented the handling like with other PureBasic objects (e. g. Gadget).

For example, if you use ButtonGadget (#GadgetNr, x, y, width, heigth, text$[, flags]) you can either assign a unique gadget number yourself or use #PB_Any. Then the command itself returns a unique gadget number.

With this number you can perform an IsGadget() query without any problems.

So why not

Code: Select all

Allocatememory (#MemoryNr, Size)
?

Assuming you are using Allocatememory (1,1024), the command could immediately return the memory address as a result (because you delivered the unique memory number by itself -> 1).
If you use Allocatememory (#PB_Any, 1024), the command would return the unique memory number and a command

Code: Select all

MemoryAddress (MemoryNumber)
would be needed to get the real memory address.

This would be close to the logical structure of the other PureBasic objects and therefore an

Code: Select all

IsMemory (memory number)
command would also be possible without any problems.

PS: I have thought about it several times and now I have to add the following information to my text above:

The problem with the ambiguous *MemoryID or MemoryNumber is only solved if AllocateMemeory() always returns a MemoryNumber by itself. With AllocateMemory(MemoryNumber, Size) only #PB_Any should be used as MemoryNumber (you could omit the first parameter, but the function must only return the MemoryNumber). PureBasic must then ensure that a new, previously not used MemoryNumber is always generated until the end of the program. The best way to do this is to always increase the memory numbers by one after every AllocateMemory() or FreeMemory() call until the exe quits.

Code: Select all

MyMem = Allocatememory(1024)  ; returns the MemoryNumber 1 for example
Debug MyMem ; prints 1
Debug MemoryAddress(MyMem) ; returns for example $3674861, the real address of the memoryblock
Debug IsMemory(MyMem) ; will return #True
Freememeory(MyMem) ; frees the memory at address $3674861 (could return #True to show memory was successfully freed)
Debug IsMemory(MyMem) ; will return #False

MyMem = Allocatemomory(512)  ; returns the MemoryNumber 2
MyMem2 = Allocatememory(1024)  ; returns the MemoryNumber 3
MyNewMem2 = ReAllocatememory(MyMem2, 4096)  ; still returns the MemoryNumber 3

Freememory(MyMem) ; could return #True to show memory was successfully freed

Debug IsMemory(MyMem) ; will return #False
Debug IsMemory(MyMem2) ; will return #True
Debug IsMemory(MyNewMem2) ; will return #True
Freememory(MyMem) ; could return #False to show memory number is not longer valid
Freememory(MyNewMem) ; frees the memory (could return #True to show memory was successfully freed

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 11:39 pm
by freak
It worked like this once upon a time. But it was much too cumbersome for simple memory allocations which is why it was changed.
kurzer wrote:For example, if you use ButtonGadget (#GadgetNr, x, y, width, heigth, text$[, flags]) you can either assign a unique gadget number yourself or use #PB_Any. Then the command itself returns a unique gadget number.

With this number you can perform an IsGadget() query without any problems.
This example has the same problem: If IsGadget() tells you that the gadget is valid you can't just call FreeGadget() on it without knowing if the gadget is still the one you created. Remember: Your use case is that somebody else might have already called FreeGadget() on it (and a new one could have taken the same ID). #PB_Any numbers can be re-used as well!

How is this for a solution:
You say the use case is an Object that creates the memory and hands it out as a result which the user can free or the object should free on cleanup. So why not also provide a dedicated "Free"-function on the same object that the user must call to free the memory? Your object can then manage an internal list of memory blocks it handed out and remove any block that was freed with the "Free"-function. Then you know exactly which memory blocks must still be freed at the end. No more guessing involved. This solution would also make your implementation more flexible. Since both creation and freeing is handled within you object, you can change the implementation (for example switch the memory alloc with a linked list or array) without any need to change the code outside the object.

Re: New command "IsMemoryID(*memoryID)

Posted: Sat Feb 10, 2018 11:46 pm
by Kurzer
that's exactly how its working now, but what prevents the user from freeing the memory at his own using FreeMemory() instead of my free memory function? In this case the FreeMemory() within the destroy method of my object would crash the programm.

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 12:30 am
by Dude
@Freak: A quick question regarding #PB_Any with gadgets: will #PB_Any ever create a gadget number that I might have used for another gadget? Or does it check if the #PB_Any number is already in use for a gadget? Thanks.

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 3:33 am
by Josh
Dude wrote:@Freak: A quick question regarding #PB_Any with gadgets: will #PB_Any ever create a gadget number that I might have used for another gadget? Or does it check if the #PB_Any number is already in use for a gadget? Thanks.
The gadget number you receive by creating a gadget with #PB_Any is the Pb intern handle for the gadget. That means, its a pointer to the memory, where Pb stores informations for this gadget. So I think, it can't be guaranteed, that this gadget number is used again.

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 12:11 pm
by freak
kurzer wrote:that's exactly how its working now, but what prevents the user from freeing the memory at his own using FreeMemory() instead of my free memory function? In this case the FreeMemory() within the destroy method of my object would crash the programm.
So? That would be a bug by the user then. What prevents the user from making some other mistake that is not related to your object and crashing the program that way?
Dude wrote:@Freak: A quick question regarding #PB_Any with gadgets: will #PB_Any ever create a gadget number that I might have used for another gadget? Or does it check if the #PB_Any number is already in use for a gadget? Thanks.
As long as your gadget exists the number will not be reused. But once you free the gadget the number can be used again for another gadget.

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 12:32 pm
by walbus
Therefore I use #PB_any whenever possible.
It works perfectly and undesirable collisions are safely avoided.
There cannot be a duplicate ID #PB_any because they are addresses.
Just as you can never get two identical addresses with AllocateMemory

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 1:11 pm
by Kurzer
walbus wrote:Therefore I use #PB_any whenever possible.
It works perfectly and undesirable collisions are safely avoided.
There cannot be a duplicate ID #PB_any because they are addresses.
Just as you can never get two identical addresses with AllocateMemory
Thats correct, but unfortunately you didn't understand what kind of problem I was referring to with my request. I didn't say there were address collisions.
freak wrote:So? That would be a bug by the user then. What prevents the user from making some other mistake that is not related to your object and crashing the program that way?
Nothing, but regarding to the "memory freed twice problem" PureBasic itself could prevent a crash like I already suggested. But as you already said, it was implemented once upon a time and Fred decided to remove this feature, so my feature request is useless at this point - c'est la vie. ;-)

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 2:40 pm
by walbus
There can't be any IsMemoryID() function, because you can and must release simple and very fast the memory with FreeMemory() and then write it to Nirvana

Such a function therefore makes no sense

Re: New command "IsMemoryID(*memoryID)

Posted: Sun Feb 11, 2018 5:03 pm
by mk-soft
You can build a own Memory functions

Code: Select all

;-TOP
;
; Memory Debugging v0.4

CompilerIf #PB_Compiler_Debugger
  
  #MemoryStop = 1
  
  Global NewMap MemID()
  
  Procedure MyAllocateMemory(Size, Flags, Proc.s)
    Protected *mem
    *mem = AllocateMemory(Size, Flags)
    If *mem
      MemID(Hex(*mem)) = *mem
    Else
      DebuggerWarning("AllocateMemory: Out Of Memory : Proc /" + Proc)
      CompilerIf #MemoryStop : CallDebugger : CompilerEndIf
      ProcedureReturn #False
    EndIf
    ProcedureReturn *mem
  EndProcedure
  
  Procedure MyFreeMemory(Memory, Proc.s)
    If FindMapElement(MemID(), Hex(Memory))
      FreeMemory(Memory)
      DeleteMapElement(MemID())
      ProcedureReturn #True
    Else
      DebuggerWarning("FreeMemory: Memory not exists : Proc /" + Proc)
      CompilerIf #MemoryStop : CallDebugger : CompilerEndIf
      ProcedureReturn #False
    EndIf
  EndProcedure
  
  Procedure MyMemorySize(Memory, Proc.s)
    If FindMapElement(MemID(), Hex(Memory))
      ProcedureReturn MemorySize(Memory)
    Else
      DebuggerWarning("MemorySize: Memory not exists : Proc /" + Proc)
      CompilerIf #MemoryStop : CallDebugger : CompilerEndIf
      ProcedureReturn 0
    EndIf
  EndProcedure
  
  Macro AllocateMemory(Size, Flags=0)
    MyAllocateMemory(Size, Flags, #PB_Compiler_Module + "/" + #PB_Compiler_Procedure + "() - Line " + #PB_Compiler_Line)
  EndMacro
  
  Macro FreeMemory(Memory)
    MyFreeMemory(Memory, #PB_Compiler_Module + "/" + #PB_Compiler_Procedure + "() - Line " + #PB_Compiler_Line)
  EndMacro
  
  Macro MemorySize(Memory)
    MyMemorySize(Memory, #PB_Compiler_Module + "/" + #PB_Compiler_Procedure + "() - Line " + #PB_Compiler_Line)
  EndMacro
  
CompilerEndIf

;- test

Procedure Main()
  *mem1 = AllocateMemory(1024)
  ;*mem2 = AllocateMemory(2048)
  
  Debug "Size 1: " + MemorySize(*mem1)
  Debug "Size 2: " + MemorySize(*mem2)
  
  Debug "Free 1: " + FreeMemory(*mem1)
  Debug "Free 2: " + FreeMemory(*mem2)
EndProcedure : main()