We can use memory functions (
realloc) the same way you would use them in C, and use them via the pointer API.
Here's an example:
from pointers import malloc, free ptr = malloc(28) # 28 is the size of integers larger than 0 free(ptr)
We can dereference the same way we did earlier, but first, we need to actually put something in the memory. We can do this via data movement:
from pointers import malloc, free ptr = malloc(28) ptr <<= 1 print(*ptr) free(ptr)
Data movement is much safer when using memory allocation, since we aren't actually overwriting memory tracked by Python.
We also aren't overwriting any existing objects, we are just putting the object into a memory space.
Here's a quick example:
from pointers import malloc, free ptr = malloc(28) ptr <<= 1 print(*ptr) ptr <<= 2 print(*ptr, 1) # prints out "2 1", since we dont have to overwrite the 1 object itself! free(ptr)
We can bypass size limits the same way as before, but again, this is extremely discouraged. Instead, we should use
realloc function works a bit differently in pointers.py. We don't reassign the pointer like you would in C:
ptr = realloc(ptr, 28)
Instead, we can just call
realloc on the object directly, like so:
from pointers import malloc, realloc, free ptr = malloc(24) ptr <<= 0 realloc(ptr, 28) free(ptr)
Identity of objects in CPython are defined by their memory address, so using
is on objects inside allocated memory won't work properly:
from pointers import malloc, free import sys text: str = "hello world" ptr = malloc(sys.getsizeof(text)) ptr <<= text print(~ptr is text) # False
We can allocate an array using
from pointers import calloc, free ptr = calloc(4, 28) # allocate an array of 4 slots of size 28
You can (somewhat) use an allocated array as you would in C:
from pointers import calloc, free array = calloc(4, 28) for index, ptr in enumerate(array): ptr <<= index print(ptr) # prints out "1"
Objects can be put on the stack using
from pointers import stack_alloc, StackAllocatedPointer @stack_alloc(28) def test(ptr: StackAllocatedPointer): ptr <<= 1 print(*ptr) test()
The difference between
stack_alloc is that
acquire_stack_alloc automatically calls the function, whereas
stack_alloc makes you call it manually:
from pointers import stack_alloc, acquire_stack_alloc, StackAllocatedPointer @stack_alloc(28) def a(ptr: StackAllocatedPointer): # you need to call a manually ... @acquire_stack_alloc(28) def b(ptr: StackAllocatedPointer): # this is called automatically by the decorator ...
Stack allocated pointers cannot be deallocated manually, meaning the following will not work:
from pointers import acquire_stack_alloc, StackAllocatedPointer, free @acquire_stack_alloc(28) def test(ptr: StackAllocatedPointer): free(ptr) # ValueError is raised
Instead, it will be popped off the stack at the end of the function. Read more about that below.
Why not a context?
Warning: This section is for advanced users.
We have to go through a couple different reasons on why we need to use a decorator instead of a context for stack allocations.
First, we need to understand how functions are handled in ASM (at least on GNU compilers).
Lets take a look at this piece of x86 ASM as an example (32 bit):
global _start _start: push 123 ; 123 on stack call func ; call our function mov eax, 1 ; system exit mov ebx, 0 ; return code int 0x80 ; call kernel func: push ebp ; push base pointer onto the stack mov ebp, esp ; preserve current stack top mov esp, ebp pop ebp ; restore base pointer ret ; jump to return address
This function does nothing, but as you can see with the
pop instructions, we are using the stack to pass parameters and store the return address of the functions.
When we put something on the top of the stack in Python, we still need to follow these rules. The memory is popped off the stack at the end of the C function (in this case, its pointers.py's
run_stack_callback), so we cannot use it elsewhere.
Now, how does this relate to using a context or not?
Python context managers are done like this:
class MyContext: def __enter__(self): ... def __exit__(self, *_): ... with MyContext() as x: ...
Remember, the data is popped off the stack after the end of the C function, not the Python function, meaning we can't just allocate and then store in the class. We also can't just do it all from
__enter__, since the allocated memory will be destroyed at the end of it.
We also can't do it from a C based class, since that will still have to return the object.
Note that there's also no yielding in C, so we can't return early either.