RichardE - It is quite unusual for embedded programs to use dynamic memory.
I strongly agree dynamic allocation and garbage collection are very useful and valuable features, but I don't agree that it is more important when RAM is scarce.
The main problems are:
1. It is extremely hard to test a program of even moderate complexity, and know that under all circumstances it will not run out of memory and break. Dynamic allocation makes this worse than simple static allocation and stack frames. This is a 'show stopper' for some developers.
2. Having managed memory can consume quite a lot of RAM in various overheads or fragmentation. This waste can be in the form of management overhead (which is needed to be able to manage memory), 'internal' fragmentation, and 'external' fragmentation.
Typically the smallest block that can be managed is some minimum-size because it needs to have a pointer to other blocks (4 bytes), and extra management typically the size of the block. The size must be held somewhere, so the correct size of block can always be recovered by free. Maybe we limit this to a 2 bytes. So say 6 bytes are used in every block of memory for management. Not too bad if user-objects are large, but very bad if user-objects are small.
The block might need to be aligned for the worst case data type (maybe an 8 byte double), which may make it impossible to use blocks smaller than 8 bytes.
To make things easier to manage, it is common to allocate memory in multiples of minimum-size. So if the whole block isn't full, the space is wasted. If the minimum block size were 8-bytes, then, on average 4 bytes would be wasted. That is 'internal' fragmentation - there is unused space, but you can't give it to the user's program because it can't be managed.
As blocks are freed, and then reallocated in random patterns, free space will appear between the other blocks which are in use. In languages like Java or Python, this can be solved, because the Java/Python VM could move the used blocks around to collect together the unused space. Normally in C or C++, those used blocks can't be moved. So there may be enough total free space to satisfy a new request, but because it is fragmented by in-use blocks, the request can't be satisfied. That type of wasted space is often called external fragmentation.
1. It is hard to test programs and know memory fragmentation will not cause them to fail
2. Memory management and garbage collection have overhead, and can't always make good use of RAM dues to fragmentation.