In Windows you have to explicitly specify which symbols to export, and only those exported ones can be imported.
The “everything is exported” default can be fixed with a flag, you then set symbol visibility with attributes like __declspec or use a linker script. This doesn’t change the calling convention, unlike with DLLs.
In contrast, both Linux and Darwin have only one C runtime library for the system, in libc.so.6 (typically) or libSystem.dylib respectively, which maintains backwards compatibility over a longer time period. Sometimes there’s a need to make changes to libc that would normally be ABI-breaking, e.g. when off_t was changed to be 64 bits to support >4GB file sizes on 32-bit platforms. But to avoid breakage, special mechanisms are used to provide two different versions of the same symbol within the same library, for each affected symbol; existing binaries will use the old version, while programs compiled and linked on a newer system will automatically use the new version. (On Linux, a complicated mechanism called symbol versioning is used for this, while Darwin just renames symbols with the asm() directive in header files.)
On Linux, it is possible to statically link any of various libcs, but programs that do that typically can’t use shared libraries * at all*, so the issue of malloc across library boundaries doesn’t come up.
Oh, and for the record, apparently Windows 10 has a new “universal” CRT DLL for new code that will be maintained in place going forward, but it’s still separate from all the pre-existing versioned DLLs, and there’s a debug variant that’s a separate DLL from the normal one and probably doesn’t share state with it.
In contrast the "universal" CRT is not very universal at all, and a horrible bloated mess. But certainly not unusual of MS...
That's what the LocalAlloc/LocalFree functions are for.
As userbinator noted, Unix has an awful tradition of exporting most symbols by default, which is also in effect on macOS. The modern approach is to pass -fvisibility=hidden to the compiler, which changes the default to unexported, and then mark symbols that are supposed to be exported with __attribute__((visibility(“default”))). Alternately, there’s a Darwin-specific ld option (-exported_symbols_list) where you write a list of names to export in a separate text file, and everything else is forced to be unexported.
My guess would be to facilitate debugging, where you might want to invoke a symbol that wouldn't otherwise be used at runtime (e.g. some sort of debug print).