4 ms·
This is unavoidable in any language that (supports dynamic memory allocation and) moves dynamic memory allocation into a library. And it goes even further than
by Someone 4y ago
This is unavoidable in any language that (supports dynamic memory allocation and) moves dynamic memory allocation into a library.
And it goes even further than the article claims: even functions that allocate a flat structure on behalf of the caller and return it should provide a companion function to free it. Reason is that the caller and the called function might have a different idea about what the memory allocator is. That’s rare on unixes, but was reasonably common on Windows with cross-DLL calls (https://codereview.stackexchange.com/questions/153559/safely-handling-memory-allocation-across-dll-boundaries-in-windows https://codereview.stackexchange.com/questions/153559/safely...)
Also, say a DLL function returns a char pointer containing a string. How would you know whether to call free or delete on it? Or, maybe, the equivalent of free in Frob, the language that DLL happens to be written in?
- pjmlp 4y agoOn Windows it is clear, memory allocated by DLLs belongs to them, and should be deallocated by APIs exposed by them, and to play safe you should use Win32 APIs for memory management and not rely on the free()/malloc() provided by the compiler.
- josephcsible 4y ago> the free()/malloc() provided by the compiler Nitpick: they're provided by the C runtime library.
- pjmlp 4y agoWhich on non-UNIX platforms means the C compiler that one bought, not necessarly from the OS vendor, as libc isn't traditionally part of the OS APIs.
- josephcsible 4y agoI don't think there's a perfect 1:1 correspondence between compilers and libc versions on Windows today, but even if there were, it's still a distinction worth making. For example, if two libraries both statically link the C runtime, that counts as separate ones (and so a malloc in one paired with a free in the other will wreak havoc), even if they're the exact same version.
- cesarb 4y ago> and to play safe you should use Win32 APIs for memory management Which ones? HeapAlloc/HeapFree? LocalAlloc/LocalFree? GlobalAlloc/GlobalFree? CoTaskMemAlloc/CoTaskMemFree? VirtualAlloc/VirtualFree? Something else? If the answer is HeapAlloc/HeapFree, which heap? Should you enable the low-fragmentation heap or not?
- pjmlp 4y agoDoesn't matter to the caller, because they aren't supposed to be clever and call any of them instead of the APIs exposed by the respective DLL for resource management. The DLL authors should better know what APIs to call internally on their own code.
- masklinn 4y ago> How would you know whether to call free or delete on it? By making ownership part of the API (and ABI). Sadly C is unable to express this, and thus so are FFI layers.
- legalcorrection 4y ago
- olliej 4y agoI'm unsure why you're saying there's rust spam here? "lifetime" is not a rust only concept, syntactic lifetimes might be, but the idea of C APIs specifying the lifetime of a returned value is not new, novel, or rust specific. Many C APIs have SomeLibraryCreateObject(...), SomeLibraryRetainObject(...) and SomeLibraryReleaseObject(...) - or a more basic but less flexible SomeLibraryCreateFoo() SomeLibraryFreeFoo(...). The important thing is that the API specifies the lifetime of the returned value, idiomatic APIs do stuff like "SomeLibraryGet..." does not transfer ownership, "SomeLibraryCreate..." "SomeLibraryCopy..." etc do. Generally this works more robustly with some variation of refcounting, but you can be copy centric and say "if you want to keep this data call SomeLibraryCopy(..)".
- KerrAvon 4y agoC is unable to express this as a machine-readable attribute, but you can certainly document it as part of the API contract and teach the FFI layer about it. This doesn’t scale, but an FFI layer rarely translates directly into the language’s idiom without some manual effort.
- messe 4y ago> This is unavoidable in any language that (supports dynamic memory allocation and) moves dynamic memory allocation into a library. Not necessarily. The Zig[1] standard library forces callers to provide at runtime an allocator to each data structure or function that allocates. Freeing is then handled either by calling .deinit()—a member function of the datastructure returned (a standard convention)—or, if the function returns a pointer, using the same allocator you passed in to free the returned buffer. C's problem here is it doesn't have namespaces or member functions, so there's a mix of conventions for what the freeing function should be called. C++ allows this as well for standard library containers, although I've rarely seen it used. > Also, say a DLL function returns a char pointer containing a string. How would you know whether to call free or delete on it? Or, maybe, the equivalent of free in Frob, the language that DLL happens to be written in? I have to concede this one. I can't see a way out of this other than documentation. [1]: https://ziglang.org/ https://ziglang.org/
- nine_k 4y agoYou provide a memory allocation interface either way: it may be a special function per type, it may be a generic allocator.
- thechao 4y agoIf `push_back()` & friends took an (optional) extra allocator parameter, it'd be pretty ideal. It'd be nicer if the implementations were forced to be single-word containers like Stepanov wanted...
- messe 4y agostd::vector can take an allocator as a template parameter though? For a list, sure I can see that having a separate allocator per element could work, but for a vector, surely you'd want the same allocator for the entire range? EDIT: assuming we're talking about C++, if not please don't hesitate to corrrect me.
- thechao 4y agoStateful allocators require a word be dedicated to the allocator in the header. In my use cases, I always have access to the allocator, but I need to create a lot of containers — most staying empty, to boot! Paying the extra overhead is morally objectionable.
- dathinab 4y agoWhile not common to run into bugs because of it on unix semantically its a problem all the time. While multiple statically linked C libs normally use the same allocator, the moment you link in any other language in any way (static,.so) the guarantee is gone. So you `dart:ffi.allocate` `C-malloc` and rust `std::alloc::alloc` might in the end all use different allocators or might happen to use the same allocator, but as long as you don't carefully control all parts involved the all bets are off. And it can make a lot of sense to use different allocators in FFI-libraries in some use cases (mainly as a form of optimization).