How does microcontroller keep track of heap? - Page 1
Forum section: electronics / general

author: hal9001
author_rank: Regular Contributor
timestamp: July 04, 2025, 09:25:16 am
content: In a microcontroller without any OS, how does the microcontroller keep track of where a malloc will point to in the heap?Code:char *x;char *y;char *z;x=(char*)malloc(10);y=(char*)malloc(10);free(x);z=(char*)malloc(10);After free(x), are subsequent mallocs able to use the memory that was allocated for x or is it blocked because of malloc for y?

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: July 04, 2025, 09:49:13 am
content: It depends entirely on the implementation of malloc and free in the library you're using.In general, dynamic memory, especially freeing dynamic memory, in embedded systems should be avoided if at all possible.A fixed number of constant-sized mallocs at startup is fine, though this would be better done with fixed allocations at compile time, but as soon as you can allocate variable amounts, or need to free & reallocate, you need to be able to deal with what happens when it fails - fragmentation, running out etc.This can make systems very difficult to test and problems hard to diagnose.Small embedded systems are typically sufficiently constrained that dynamic memory is unnecessary - there are usually better alternatives.

author: hans
author_rank: Super Contributor
timestamp: July 04, 2025, 09:58:05 am
content: That depends on the implementation of the heap.Although malloc() could scan through all gaps inside the heap and pick the best slot, this is quite expensive (in runtime) to do.Its more likely malloc() will append to the end and then wrap around.So in this isolated example, z could be allocated at the location of x. But more likely its appended after y.If you're allocating different sized objects, you'll get heap fragmentation as gaps will remain when free() is called and that spot is taken up again. For example, say you have a heap of 40 bytes.You allocate x, y, z, each 10 bytes. So after z, there is still 10 bytes free.You then free x and y. Its likely that 'z' is placed after half of the heap buffer, so before z is 20 bytes free, and after z 10 bytes free. Total: 30 bytes free.Now you want to allocate 21 bytes on the heap. Theoretically this should fit, but there is no continuous region inside the heap large enough, so malloc fails.Now imagine doing this for even more various sized objects, with a chaotic object lifetime.It depends on how much heap memory your program will use at once, with how much degree of fragmentation will accumulate over a long time, and the resulting actual heap you'll need to make it work. I'm not aware of an easy way to calculate this, but I imagine simply overallocating a heap by say x2 or x4 times is not thorough enough to make it work.Most embedded programs don't have the luxury the reboot themselves when malloc() runs dry. This is annoying on desktops, but often not a deal breaker.

author: brucehoult
author_rank: Super Contributor
timestamp: July 04, 2025, 10:33:53 am
content: Quote from: hal9001 on July 04, 2025, 09:25:16 amA microcontroller doesn't have any idea that such a thing as a "heap" even exists. Nor does a full on microprocessor running Linux, Mac, Linux etc.Everything is entirely up to your program, or the malloc() and free() library you link your program with.You are free to write your own malloc() and free() functions, and they will be used instead of the library ones if you do.The usual way to do things is that once the program code and global variables are set up, the remaining block of RAM is use for the heap and stack, with the stack pointer starting from the highest address and working down, and the heap_end pointer (there is no standard name, but it is also often referred to as the "program break") starting at the lowest address and grows upwards.Ifheap_endandstack_pointerever cross over then you have big problems.The simplest implementation is that everymalloc(N)addsNtoheap_endand returns the originalheap_endvalue.free()does nothing.This is in fact perfectly adequate for probably a majority of embedded software, which allocated memory at startup but never frees anything.From there you can add infinite amounts of complexity tomalloc()andfree()to try to get memory to be reused as much as possible, with minimum waste. In any system or programming language (e.g. C) where you don't know where all the copies of a pointer are it is impossible to do a perfect job of this as you can't move existing objects around.You will almost never see a system that moves heap_end to a lower address if objects are freed.On a machine with an operating system, moving heap_end upwards often needs a call to an library function such assbrk()which requests more physical RAM from the OS if needed.

author: sleemanj
author_rank: Super Contributor
timestamp: July 04, 2025, 11:51:06 am
content: Standard AVR libc implementation walks the free list looking for the smallest free chunk >= the requested size. If it's == the requested size, job done. if it's > the requested size, either just enlarge the request or split the chunk. If there's nothing in the free list, allocate more on the end.Freeing does reduce the heap end marker (brkval) if it can.Other implementations will differ as stated, the implementation of other common microcontrollers (ESP32, RP2040) I have searched, is surprisingly complicated and convoluted implementation, for what you'd think to be a simple task.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: July 04, 2025, 12:43:10 pm
content: Quote from: sleemanj on July 04, 2025, 11:51:06 amHeap allocation is not "a simple task". Sure, first time allocation of some memory is simple, but the designers of the libraries can not know how you are freeing up blocks of memory and re-allocating them. The complexity you observed has nothing to do with the first time allocation, but it's all about attempts to reduce memory fragmentation over time, And this has been a problem (and studied for) around 50 years now.I wholeheartedly agree with mikeselectricstuff. Freeing / re-allocating memory is best completely avoided at all for microcontrollers. You could use "one time malloc" in the beginning, but you might as well declare some (global) arrays in the beginning, so you don't have to waste flash space on the malloc code. I once did some maintenance (professional, i.e. "for work") maintenance on firmware for an embedded system (about 1MB of Flash, and 1MB of RAM). and this system did some different sort of data gathering and analysis tasks. These tasks needed most of the memory, but were never run at the same time. So after startup, it just created a big array to hold the data for all these tasks, and this single databuffer was re-used for several different task that needed to temporarily save a lot of data.

author: tggzzz
author_rank: Super Contributor
timestamp: July 04, 2025, 01:04:17 pm
content: Quote from: hal9001 on July 04, 2025, 09:25:16 amIt doesn't. It can't. It isn't the MCU's responsibility.It is the responsibility of the software executing in the MCU.

author: brucehoult
author_rank: Super Contributor
timestamp: July 04, 2025, 01:29:45 pm
content: Quote from: sleemanj on July 04, 2025, 11:51:06 amAhahahahaha.Making a malloc() that works well in a wide range of situations is VERY complex.Code:bruce@i9:~$ size /lib/x86_64-linux-gnu/libjemalloc.sotextdatabssdechexfilename79658924528222490430460212e7a85/lib/x86_64-linux-gnu/libjemalloc.soOver 3/4 of a megabyte for the most popular high performance malloc.Code:bruce@i9:~$ size /lib/x86_64-linux-gnu/libgc.so.1textdatabssdechexfilename1735302568255000431098693fa/lib/x86_64-linux-gnu/libgc.so.1Boehm GC (my own preference, better than GNU malloc) is quite a bit smaller, but still not small.Any malloc library you can even fit in a typical microcontroller is going to be crude, low performance, and unable to use the most powerful tools against fragmentation under repeated malloc/free because of a severe lack of space to manoeuvre in.One of the lessons I learned the hard way, many years ago, writing my own custom malloc/free for long-running applications on constrained devices, is to NEVER split up a large block that has been freed. If it needed a large block once then it will probably need one many times, and it's best to be able to use the same one again, not find that it's been split up and have to allocate new memory. That leads very quickly to memory exhaustion, even though there is lots of "free" memory -- in unusably small chunks.Well, I have a lot of lessons.Another is that it is EXTREMELY valuable to have a call that allows the app to tell the memory allocator that it is finished init and is about to enter the main loop. But that is very non-standard in malloc APIs.Another is to not allow the app to make allocations over a certain size -- maybe 4K or 64K on a big system, perhaps 64 or 512 bytes on a microcontroller. Just fail any such call. And document this of course. Force the application programmer to split large objects explicitly into linked lists or trees of smaller objects (or whatever they want to come up with). I have my own library that manages this and makes a multi-level tree of blocks act like a contiguous array.

author: tggzzz
author_rank: Super Contributor
timestamp: July 04, 2025, 01:59:07 pm
content: Quote from: sleemanj on July 04, 2025, 11:51:06 amMany neophytes think that, mainly because they are ignorant of themany decadesof experience indicating the exact opposite.As Philip Greenspun didn't quite say... Any sufficiently complicated C contains an ad hoc, informally-specified, bug-ridden, slow implementation of a garbage collector.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 04, 2025, 02:50:25 pm
content: Quote from: hal9001 on July 04, 2025, 09:25:16 amAssuming you more or less know how malloc() operates in general, the basic way it grabs heap memory at a low level is by calling _sbrk().On simple embedded devices, the usual way _sbrk() is implemented is like so:https://github.com/eblot/newlib/blob/master/libgloss/epiphany/sbrk.cQuote from: hal9001 on July 04, 2025, 09:25:16 amYes, a very common implementation of the C std library for embedded targets is newlib, and it implements a full-fledged malloc(). Freed memory is free to reuse, but with the limitations common to all malloc() implementations: it creates memory fragmentation, and if there is no free, contiguous block large enough to accomodate a given new malloc(), even if the total of free heap memory is larger than what's required, allocation will fail. So on small embedded systems where there isn't a lot of heap memory available, that's a problem you can quickly run into if you make a lot of allocations and deallocations.I really advise against using malloc() on "microcontrollers" in general, not just for reliability reasons (risks of memory leaks, use after free, etc), but for the reason above. Because even if you have zero allocation bug in your code, allocations can start to fail after a number of malloc()/free() cycles due to fragmentation. And it's also rarely useful in practice. You can do with static allocation and some arena allocator if you need to reuse some memory dynamically. Arena allocators are dead simple and very useful for that purpose.Note that using C++ is tricky for that reason, as it's very easy to write code that will trigger heap allocation without you necessarily realizing it. In particular, most of the STL containers can't be used. There are alternatives that use static allocation only, but that's quickly a rabbit hole. So if using C++, be prepared to do your "homework". It's going to be a different experience that on a desktop PC.With C on embedded targets, the std functions that can malloc memory behind your back are usually all printf() functions. If you want no heap allocation at all, you'll have to avoid these and replace them with alternatives. If you want the same interface, there are ready-to-use alternatives such as nanoprintf (and others).

author: peter-h
author_rank: Super Contributor
timestamp: July 04, 2025, 03:04:58 pm
content: A search on e.g. sbrk will find some long previous threads on e.g. the Newlib heap code which I implemented on my 32F417 project.That one maintains some sort of linked list of the blocks.Fragmentation is very possible with this, so I never free() any block, with the exception of a 60k block used by MbedTLS which is so big that fragmentation is impossible via repeated malloc() and free() of this block.That heap code also has a bug in that it doesn't work unless you first allocate the entire heap and then free it. So I have startup code which allocates and frees ever bigger blocks until it hits the end

author: Doctorandus_P
author_rank: Super Contributor
timestamp: July 04, 2025, 03:11:02 pm
content: Quote from: peter-h on July 04, 2025, 03:04:58 pmAgain: If you don't use free(), there is no benefit of using malloc() either. You just declare global / static arrays.What use is it to repeatedly malloc() and free() the same big block of memory? Can you give any explanation of why doing this would be better then just static memory allocation? (For an embedded system, PC software is different here).

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: July 04, 2025, 05:14:32 pm
content: Quote from: Doctorandus_P on July 04, 2025, 12:43:10 pmAnd if you need your buffer to have different types depending on the task, unions can be useful, just only let one task use it at a time!

author: Silenos
author_rank: Regular Contributor
timestamp: July 04, 2025, 05:16:26 pm
content: Quote from: Doctorandus_P on July 04, 2025, 03:11:02 pmIirc mbedtls specifically requires heap to operate. I had to run it on esp32 once, this feature was one of primary to made me never ever attempt it again.

author: peter-h
author_rank: Super Contributor
timestamp: July 04, 2025, 07:52:04 pm
content: QuoteMalloc() is suitable for allocating RAM for optional product features, activated according to a config file, parsed in main(). You can't use static alloc then.QuoteIt runs its own private heap (as does FreeRTOS and quite a few other things) inside a block of RAMyougive it. That block can be statically allocated, or it can be allocated on your own heap. Or MbedTLS can also directly use your own heap (not ideal, since it dumps its own heap when a connection is closed, and that could fragment up your heap if it was using that. Sure MbedTLS is horrible but X509 is a huge bloated contraption and if somebody needs it, you have to do it somehow...

author: ejeffrey
author_rank: Super Contributor
timestamp: July 04, 2025, 08:27:19 pm
content: Quote from: peter-h on July 04, 2025, 07:52:04 pmAlso for libraries that have features that might never be used. For instance, C stdio requires a buffer for each used, buffered file descriptor. You might never use stdio, use it unbuffered only, you might have a single buffered stdout for a serial console, or you might use several file pointers created in main() and used for the life of the program. Using malloc() allows you to use exactly as much memory as you need. There are other ways to accomplish the same thing, but malloc is an acceptable way.Some people will say you shouldn't do any of this in an "embedded" system but embedded systems is such a broad category now that you can't really make any such general claims. There are "embedded microcontrollers" that are in all ways more powerful than the first 4 PCs I owned.

author: harerod
author_rank: Frequent Contributor
timestamp: July 04, 2025, 09:37:02 pm
content: Since we try to avoid malloc/free in small embedded environments, other memory management concepts are required. We want to know at compile time if enough memory is available for every implemented function. If we need to repeatedly allocate and free buffers and want to avoid fragmentation, we could use fixed-size buffers. Widely known example: lwIP.

author: SteveThackery
author_rank: Super Contributor
timestamp: July 04, 2025, 09:48:14 pm
content: Quote from: ejeffrey on July 04, 2025, 08:27:19 pmYeah, but I don't think it's the power that matters; it's the fact that your first four PCs were running an operating system like Windows or Linux (or maybe CP/M, OS/2, or whatever). To my mind, that is the biggest difference when it comes to memory management.

author: SteveThackery
author_rank: Super Contributor
timestamp: July 04, 2025, 09:59:29 pm
content: Quote from: peter-h on July 04, 2025, 07:52:04 pmIs this a real case, though, or a theoretical one? It only works as an argument if the product features are mutually exclusive. By that, I mean there isn't enough memory to support all the features simultaneously. Can you give an example of areal productthat has one or more optional features, activated by a config file, but which can only support a subset of these features at any one time? It seems a bit contrived to me, but I'm happy to be proved wrong.

author: Rick Law
author_rank: Super Contributor
timestamp: July 05, 2025, 02:41:00 am
content: Quote from: SiliconWizard on July 04, 2025, 02:50:25 pm+1 to that. "Std functions doing malloc memory behind your back" is a time-bomb in waiting.Besides printf() state above, I would widen the alert to: Anything function that returns a pointer you didn't pass should be a suspect of memory malloc/alloc abuse.For example, strdup(). It makes a copy of your string and return to you a pointer to the new copied string. It is giving you back a pointer that you did not pass to it. It has to get the memory from somewhere. Depending on the library, the library's function description may not even discuss if a returned pointer should be free() or can be free(). So, these kind of functions should be use with care or avoided.

author: brucehoult
author_rank: Super Contributor
timestamp: July 05, 2025, 03:02:03 am
content: Quote from: Rick Law on July 05, 2025, 02:41:00 amOr are not reentrant, returning a pointer to a static buffer. There are a lot of libc functions like that.strtok, gettime, localtime, asctime, getpw*, getgr*, gethostbyname, gethostbyaddr, strerror, rand, readdir, crypt.I think all those have _r reentrant variants.

author: ejeffrey
author_rank: Super Contributor
timestamp: July 05, 2025, 04:34:37 am
content: Quote from: SteveThackery on July 04, 2025, 09:59:29 pmI still think the most common case will be libraries, but I have a real product that does exactly this. The microcontroller is acting as a supervisor and monitoring hub for the main product. It monitors a bunch of I2C sensors, SPI devices, and analog voltages. Our hardware has several configurations with different sets of sensors. For ease in configuration management efficiency we use a single binary image. At startup it reads some pin strap configuration to determine what hardware version we are running on and initialize the correct devices. That initialization includes allocating (small amounts of) memory for each device. In our case we could statically allocate memory for every possible device and not run out but that would be wasteful. We could build a separate binary for each hardware version with exactly the static buffers needed but we have plenty of flash, and it's easier to maintain a single executable.And yes, someone could theoretically build an instrument with every possible sensor and use the maximum amount of memory. And maybe they would get a heap exhaustion error when they tried to use it the first time. But it won't happen in the field and I'm not going to worry about that until it happens.

author: peter-h
author_rank: Super Contributor
timestamp: July 05, 2025, 06:36:01 am
content: QuoteIndeed, although LWIP will do funny things if you don't allocate enough buffers, or too-small ones. Not documented anywhere AFAICT. Spent months on LWIPQuoteYes; a product I sellIt is a C user-programmable comms etc box with various features which probably most people won't want, or they want to not use them in order to get more RAM for other code.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 05, 2025, 09:25:55 am
content: Quote from: Silenos on July 04, 2025, 05:16:26 pmNo, it does not. It has an optional internal memory allocator which is dead simple. You initialize it with one call, supplying a pointer to your statically allocated memory:Code:static uint8_t tlsbuf[32*1024];mbedtls_memory_buffer_alloc_init(tlsbuf, sizeof tlsbuf);I also don't understand why anyone would malloc() buffers at init and then just never free them. It works, sure, and does no harm, but is unnecessary. Just good old static allocation does the same, and you can save a bit of memory (both rom and ram) by not needing the malloc()/_sbrk() implementation. And raises less questions.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 05, 2025, 09:38:27 am
content: Quote from: ejeffrey on July 05, 2025, 04:34:37 amI have had plenty of similar projects, but so far, I have been very satisfied withunions. They will work pick-1-out-of-N alternatives:Code:union{struct { ... } voltage_sensor_state;struct { ... } temperature_sensor_state;struct { ... } inertial_sensor_state;struct { ... } barometric_sensor_state;} // 1 can be chosenIt will also work for an architecture, where multiple can be chosen mapped to a number of channels:Code:union{struct { ... } voltage_sensor_state;struct { ... } temperature_sensor_state;struct { ... } inertial_sensor_state;struct { ... } barometric_sensor_state;} channels; // two can be chosen arbitrarilyBut if such array-of-channels approach is somehow not suitable for architecture, but you want to enable random stuff here-and-there, then the malloc() type allocator is simple. The challenge is proving which combinations can work at the same time. If end-user is fine finding that out by trial and error, then it's not a problem.

author: SteveThackery
author_rank: Super Contributor
timestamp: July 05, 2025, 10:34:24 am
content: Quote from: ejeffrey on July 05, 2025, 04:34:37 amBut surely not. I would argue the opposite is true:memory which is unallocated and unused is wasteful.My point is that, if you can, you really should "statically allocate memory for every possible device". In your example there is more than enough memory, and statically allocating it eliminates the risks and complications of malloc(). Memory is not something to be saved, it is something to be used.EDIT: Correction - Iwouldargue....

author: cv007
author_rank: Frequent Contributor
timestamp: July 05, 2025, 03:04:09 pm
content: QuoteIn the case of newlib (nano specs anyway), the use of printf/sprintf will also bring in malloc whether used or not. In the case of sprintf it will not use malloc unless a variation like asprintf is used (as far as i can tell). For printf, if malloc fails (such as _sbrk returns -1) you still get output but will be unbuffered (_write len always 1).By default a simple use of printf for newlib (nano) goes something like this- printf asking _malloc_r for 1024 bytes (default printf buffer size), _malloc_r asking _sbrk for 1032 bytes, and if it succeeds printf will now have a 1k buffer. If it fails (_sbrk returns -1), then printf goes to unbuffered mode. Of course, setvbuf is available so you can easily bypass the use of malloc and still provide a buffer of your own, or turn off printf buffering and let other code do the buffering if needed (such as uart code having its own buffer). I would at the very least cut down the default size as 1k is excessive for a smaller mcu, and as long as you are using setvbuf may as well just provide a buffer of your own instead of making malloc do it. I would guess many users of newlib printf do not realize by default it uses 1k of ram for its buffer.The _sbrk function is the top level 'giver' of ram to use (call it heap or something else, doesn't matter), and never gets its back. It appears like a Boolean function- it either allows or denies the request, and if it allows will provide a ram start address from which the requested size can be used (and keeps track of what it has given away). Although _sbrk normally uses linker symbols created in a heap section to determine whether it can allow the request, your own version could also do anything it wants and use something like a static array created for that purpose (now from .bss instead of .heap, but both are ram and would do the same thing). I would imagine you could also create an _sbrk which can deal with non-contiguous ram.I use C++ and do not use the heap or allow it. Since nano specs are also in use, there is no _sbrk function so if malloc does get pulled in without my knowledge I get an undefined _sbrk symbol error. If I do have an _sbrk function available (different specs, or created on my own for some reason), the linker script will determine if the heap is allowed and unless I pass a symbol to the linker which sets the heap size there will be no heap space and _sbrk will always return -1.

author: peter-h
author_rank: Super Contributor
timestamp: July 05, 2025, 04:10:20 pm
content: Yes we have done a long thread on newlib printf and heap usagehttps://www.eevblog.com/forum/programming/help-needed-with-some-heap-test-code/https://www.eevblog.com/forum/programming/best-thread-safe-printf-and-why-does-printf-need-the-heap-for-f-etc/https://www.eevblog.com/forum/programming/heap-analysis-tool-for-embedded-systems/msg4562905/#msg4562905https://www.eevblog.com/forum/microcontrollers/32f4-hard-fault-trap-how-to-track-this-down/I think the issue lies in the use of bignum in the "perfect printf" implementation. A search on bignum turns up various threads, including an analysis of whether it is really needed. It is something that came out around 1990 when everybody moved to bignum because that is how you got numerically perfect results. In embedded, you can replace the newlib printf and scanf with much faster versions which do not use the heap. I did all that on my last project and posted the details; it wasn't easy because ST do not supply the sources, and didn't build the libs with weak symbols, so I had to weaken the entire libc.a to replace these parts.But eventually I retained the newlib heap because a heap is useful, and this one works well. Spent many weeks and weeks on this stuff...

author: Tation
author_rank: Regular Contributor
timestamp: July 05, 2025, 05:43:31 pm
content: Quote from: Siwastaja on July 05, 2025, 09:25:55 amAnd you will know, at link time, if you have enough RAM, instead of knowing it at run time.

author: SteveThackery
author_rank: Super Contributor
timestamp: July 05, 2025, 08:44:05 pm
content: ^^^^^ THIS ^^^^^

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 05, 2025, 10:44:50 pm
content: For cases where some form of basic dynamic allocation is required (or at least a plus), as I mentioned earlier, I recommend using arena allocators instead of standard "malloc". Of course, they require to be stricter in terms of handling lifetimes (if you ever need to free some objects, but not all), as you can't just free any allocated object in them as you wish, but can only go back to a given point in the arena - much like stacks, except arenas usually grow upwards and not downwards.If you only need to reuse a given memory area with objects of different types, you can otherwise just define a union variable, as some have mentioned earlier.If you need a bit more flexibility, such as a possible "array" of objects of varying types, while not wasting memory, arena allocators are handy, and again, very simple to implement. A basic introduction here:https://www.gingerbill.org/article/2019/02/08/memory-allocation-strategies-002/

author: brucehoult
author_rank: Super Contributor
timestamp: July 06, 2025, 12:02:49 am
content: Quote from: peter-h on July 05, 2025, 04:10:20 pmYou need large integers but you don't need ARBITRARILY large because IEEE floats are fixed size and can represent only a finite number of values.In 2007 I did some work on perfect floating point conversions for embedded work (actually Java on BREW feature phones) in which I proved that for IEEE double precision you never need more than 1152 bit (144 byte) integers and the total for all variables is less than 1K which you can allocate statically, or on the stack, or whatever you wish. I did both a2f and f2a and corresponded with both Guy Steele and Will Clinger about this and they agreed it was correct. Unfortunately my boss at the time would not allow me to publish.For single precision, btw, you need just 174 bits (22 bytes) for each bignum and I think 132 bytes in total for all variables (IIRC there are six).

author: Rick Law
author_rank: Super Contributor
timestamp: July 06, 2025, 02:03:02 am
content: Quote from: brucehoult on July 05, 2025, 03:02:03 amI understand your point and I agree with it. But there is a different level of evil for those creating a new buffer vs those using a static one. Actually, the two working together gets you to an even more dangerous level of evil. In fact, it was asctime() that taught me a hard lesson.The library I was using, it documented well that asctime() uses a fix buffer. There were times when I do certain modification to the results (such as replacing something like Jan 20,2001 to pure numeric so text lines in a log file can be sorted). If I strcpy() it to local variable on the call stack, that is fine but I can't return that to my caller if I am a function. But I can use (and I did used) strdup() to replicate that string, do my standard modification to it and return the modified asctime to the caller. That created a fault that is hard to find. Works when called not too many times, but number of calls vary from run to run.Nothing is more frustrating than debugging the bugs in your debug codes. It was a long time ago very frustrating bug hunt that taught me the lesson: if it give you back a pointer that you didn't pass to it, look for the landmines.

author: cv007
author_rank: Frequent Contributor
timestamp: July 06, 2025, 10:46:56 am
content: QuoteNo one is specifying which version of newlib printf they are talking about, so each is assuming the version one uses. The float version is a totally different thing than the non-float, so my statement that the heap is not required is only true for the non-float version. I tend to forget there is a float version.I don't use the non-float version either as I have my own c++ cout style formatting, but I still once in a while try to figure out what newlib stdio is doing.

author: DavidAlfa
author_rank: Super Contributor
timestamp: July 06, 2025, 06:42:01 pm
content: Dynamic allocation is perfectly fine in mcu.Maybe not in a MCU with 512B RAM, but already makes sense in 4-8K.Some MCUs boat a very large flash (128K+) with small ram, so that would expand functionality by a lot.I.E: I could use a mp3 or opus decoder, statically alocating them is a waste, just allocate the required one.Or in dynamic GUIs using widgets etc, allocating every single element is stupid when you're only showing one current screen at a time.For me the only issue was a weird fragmentation issues with ST lib, alloc would threat every alloc independently, i.e. allocated/freed 10+10K, allocating 18K wouldn't use this memory, but place it next to the other two 10K blocks.Then I had 10+10+18K blocks that would only hold those sizes or less.Fixed it by allocating all the ram at boot (In a single malloc call) then freeing it, for whatever reason this made alloc behave as it should.Else than that, I was very pleased with it!There're lots of efficient MCU memory allocators in github...

author: westfw
author_rank: Super Contributor
timestamp: July 06, 2025, 09:13:49 pm
content: QuoteMy rule of thumb is that the complexity of malloc() increases until one MUST implement simpler alternatives.chunk_malloc()to quickly allocate chunks of memory from a set of fixed sizes.getbuffer()to quickly allocate packet buffers (usually from queues that are also quantized in sizes.)Something for dealing with strings, that is automatically garbage-collected.etc.

author: tggzzz
author_rank: Super Contributor
timestamp: July 06, 2025, 09:29:37 pm
content: Quote from: westfw on July 06, 2025, 09:13:49 pm... And free() approaches the complexity of a GC of the type found in 1970s textbooks. With equally dire performance.Other languages, having a simple solid computation model, have made 40 years of progress since then.

author: peter-h
author_rank: Super Contributor
timestamp: July 07, 2025, 07:10:58 am
content: The thing is, there is no free lunch. If you allocate various funny size blocks, presumably consecutively in RAM, and then free some, you will get funny size holes. That is logically unavoidable.The reason "modern" languages have better heap management is not because they magically solve this problem. It is because they get some 16GB RAM plus to play with, so if you waste a gig or two, nobody notices and the machine gets rebooted long before it might become a problem. Even if you have uptime of a year, so much RAM will still solve the problem.And most things need rebooting after some time because something has stopped working, which conceals memory leaks. I "run" several servers, some Centos, some Linux, some running NGINX and some Apache, and even though the uptime is awesome, after a year you find something weird got broken e.g. some certificate renewal cron job mysteriously stopped working, and a reboot fixes it. I had one the other day, on a server with uptime of maybe 5 years (very simple, fixed HTML, no PHP etc) where an activity check ping cron job stopped working.Look here for an example of a "modern" languagehttps://www.eevblog.com/forum/microcontrollers/is-st-cube-ide-a-piece-of-buggy-crap/msg5964343/#msg5964343There are heap algorithms which avoid fragmentation e.g. power of two sizing but I am not sure anybody uses them on "PC" apps.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 07, 2025, 10:07:27 am
content: Quote from: peter-h on July 07, 2025, 07:10:58 amTheoretically, "modern" languages can do things like allocate a non-contiguous object - think about an array which has gaps - so that compiler handles accesses to it transparently. This of course incurs runtime performance penalty with every access - but as these modern languages would do stuff like run-time range checking, there is penalty anyway.The C way, where memory offset can be calculated as index * sizeof type, and structs are contiguous in memory, is both very simple and very efficient, but leads to memory fragmentation when dynamic allocation is combined with relatively small amounts of memory.So yeah, no free lunch. Usually the best in terms of performance and memory use is application-specific custom allocator / data structure design, which would be what you do in C, but this is also most work. Modern languages somehow tend to come with quite a lot of bloat for what they achieve.On desktop computers this isn't a problem since they have nearly infinite amount of virtual memory available, CPU handles virtual <-> physical memory mapping efficiently, and OS kernel handles virtual memory management. Therefore, large and small contiguous blocks are always available for malloc(), even though they might not be contiguous in physical memory!

author: brucehoult
author_rank: Super Contributor
timestamp: July 07, 2025, 11:55:30 am
content: Quote from: Siwastaja on July 07, 2025, 10:07:27 amI tend to disagree. "Modern" languages allocate things continuously, but via strict type checking they know where all the pointers are and have GCs that can move objects around to compact memory, and update the pointers. You can't do that in C/C++ but you can in Java, C#, Go and of course all the interpreted languages.I use non-contiguous allocation of all objects larger than a threshold (e.g. 64 bytes or 512 or 4096 bytes, depending on total RAM size, let's call it "BS") on "small" machines, in C. I normally use a 2-level structure, with up toP = BS/sizeof(void*)pointers in a single top level block, andPblocks ofBSbytes each, except possibly the last one. So the maximum object size isP*BS = BS^2/sizeof(void*).On a little AVR 328 with 2K RAM I'd use 32 byte maximum block size (and with arenas for blocks divided into 4, 8, and 16 byte objects), with a maximum object size of 512 bytes. Or maybe 64 byte blocks with an maximum object size of the full 2K! On CH32V003 with 2k RAM but 4 byte pointers a 64 byte block size gives 1K maximum object size.On a 6502, Z80 etc etc with a full 64k RAM using 256 byte maximum block size (a very natural size especially on 6502) gives a maximum object size of 32k, which seems very reasonable.On a 32 bit machine using 4096 byte maximum block size gives 4 MB maximum object size, which will often be enough, but going to max 64k blocks gives max 1GB objects.In general this seems like a pretty good scheme to me, for smallish machines.Accessing a random array element, outside of a loop, needs a few instructions, which you can use a function call for, but it's not TOO big to inline if you really want to. e.g. on Cortex-M3 array indexing is 5 instructions, 16 bytes of code, and two memory accesses instead of 1:https://godbolt.org/z/MPns8fdvaCode:obj_index_int(int**, unsigned int):lsls r3, r1, #2ubfx r1, r1, #10, #20ubfx r3, r3, #0, #12ldr r2, [r0, r1, lsl #2]ldr r0, [r2, r3]bx lrOn Armv8 it's 4 instructions and 16 bytes.Checking for index out of bounds is no different to normal, and is done on the index as a simple integer, before finding the block and element.But most code processes large objects in loops, and for this I provide iterators that call provided lambdas (callback functions) with a pointer and size of contiguous bytes, together with a state object. I provide functions to iterate 2 or 3 objects in parallel, with arbitrary offsets, calling back with the largest chunk of bytes before the end of the current block in each object.So you end up with a little more code, but actually virtually indistinguishable speed, compared to normal arrays.

author: Tation
author_rank: Regular Contributor
timestamp: July 07, 2025, 01:24:27 pm
content: Quote from: peter-h on July 07, 2025, 07:10:58 amI think that the true reason modern languages solve this is not they having 16 GB, but they running under an OS/HW combo providing virtual memory. You can add Boehm GC to C/C++ solving many issues, but it still needs an OS with virtual memory.

author: brucehoult
author_rank: Super Contributor
timestamp: July 07, 2025, 01:57:59 pm
content: Quote from: Tation on July 07, 2025, 01:24:27 pmNothing about Boehm GC requires virtual memory.I used Boehm GC for the very successful (until iOS and Android came out ) "AlcheMo" Java native compiler for Qualcomm BREW phones (almost (?) everything on Verizon in those days).The smallest phone we supported with it had 390 KB of RAM but most were 1 or 2 MB. CPUs were ARM7TDMI. It used the above-described 2-level array scheme for all strings, arrays, buffers, hash tables etc. actually we ported AlcheMo to iOS, Android, and Windows Mobile. Some of the first things (including top 5 games) in the iPhone app store were native compiled Java ME e.g. "Virtual Villagers: A New Home"https://www.ign.com/articles/2008/11/01/virtual-villagers-a-new-home-iphone-reviewhttps://techcrunch.com/2007/06/20/innaworks-introduces-alchemo/

author: hans
author_rank: Super Contributor
timestamp: July 07, 2025, 03:59:28 pm
content: Quote from: Tation on July 07, 2025, 01:24:27 pmA virtual memory OS can do memory compaction to move (virtual) pages around. Modern kernels can also use modules like zram or zswap or perform in-memory compression. MacOS is one kernel that does this by default, which is why I think they blew a huge trumphet last year of '8GB on Mac is like 16GB on Windows'. While in real-life, if you use a 8GB machine with a modern video/image editor, there is a lot of data which cannot be compressed. It works to an end (at the cost of CPU performance), but its not magic.(Oh and VPS providers must hate you as a customer when you enable zswap on their cheap VPS plans. Those plans often have very limited RAM but shared "fair use" CPU cores sounds like 'free real estate')I guess in theory, the virtual address space is 48-bit on x86-64 (and minimum 48-bit on AArch64 too) whilst the physical space is 52-bit on x86-64. That's 256TiB virtual. I guess one could allocate 1TiB of zeros every day, swap it into a 256TiB ZRAM+ZSWAP device, then see what happens if 'memory runs out' after 256 daysOn a more serious note: I've a C# application running 24/7 that has 200MiB actual RAM usage, but the virtual memory size is >0.25 TiB. I've no idea what dotnet core is doing, but its stable and fast, so IDC.Point I want to make is, don't compare how the heap is managed by a desktop OS to a device that is still bare metal.

author: ejeffrey
author_rank: Super Contributor
timestamp: July 07, 2025, 05:14:39 pm
content: Quote from: peter-h on July 07, 2025, 07:10:58 amEvery significant allocator does some variation on this. They don't do power of two sizing (which would be very wasteful) but they maintain different pools of memory and different free lists for differently sized objects to avoid fragmentation. Yes it works because there is extra memory available: in a highly constrained system if you free 500 bytes and then allocate 200 bytes you might have to split the 500 byte chunk in two. In a more relaxed environment you can allocate a new 200 byte block and save the 500 byte chunk for the the next call (which might be for 500 bytes). But it's not (usually) an unbounded overhead, that just grows until you reboot or restart the application.On top of that, many runtime environments add custom allocators for their particular usage profile. The C++ STL containers are parameterized on allocators so that you can assign optimized allocators for individual containers.Of course in the end virtual memory is the silver bullet, at least for languages that don't naturally have the ability to defragment memory. The OS gives the application page sized blocks, and those can in principle come from any part of physical memory. And if memory fragmentation within an application does get bad enough that there is a bunch of unused but unreclaimable memory, the OS can eventually page it out and free up RAM.But the kernel also uses these same types of techniques to manage both it's own internal memory usage as well as to try to reduce fragmentation of the physical memory allocated to applications.

author: peter-h
author_rank: Super Contributor
timestamp: July 07, 2025, 06:54:46 pm
content: QuoteBut not with C, surely, unless you have an MMU capable of some very high resolution remapping. This is because in C you get a pointer to the block in RAM, not a pointer to a pointer to the block. So with C you cannot do "garbage collection". OTOH I am not familiar with the latest x86 motherboard capability for memory remapping.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 07, 2025, 07:07:10 pm
content: Quote from: peter-h on July 07, 2025, 06:54:46 pmI mean, that was a big selling point of Intel 80286. It's not really a new thing. Every OS has done exactly this, switch pages of memory to different physical locations or swap to drive, while keeping contiguous virtual address space visible for the (often in C) applications, for 35 years or so. Hardware support is nearly mandatory for this to function with acceptable performance, and it came with the 286.Memory fragmentation in general purpose computers isn't a big thing and as such "modern languages" do notneedto implement non-contiguous allocations themselves (that's why I wrote theytheoretically can), they just rely on having enough RAM and virtual memory paging managed by OS, just like C programs do.

author: PlainName
author_rank: Super Contributor
timestamp: July 07, 2025, 08:36:14 pm
content: Quote from: SteveThackery on July 05, 2025, 10:34:24 amI have a project which uses a configuration file to define a bunch of conditions and actions. There are inputs and outputs, both of which could have variable numbers/definitions and won't be known at compile time. Indeed, they may not be known at runtime startup. A condition will comprise a variable number of input/output/timer events/states and trigger some action, typically a desired output state or timer action.I guess it's basically a state machine whose states and transitions are only known when the configuration file is read. It could be read from ROM, removeable storage, even some user typing in stuff. I chose to implement it via my aforementioned linked lists which are dynamically allocated.There is no specific memory requirement. The config is either allocatable within the available memory or it isn't, but there is no way to know until it's tried and it either works or tells you to be less verbose.That's an actual real world application and it works very well, thanks. Has done so 24/7/365 since 1996 (except for during powercuts, when the stuff it controls would be dead anyway).

author: tggzzz
author_rank: Super Contributor
timestamp: July 07, 2025, 09:27:37 pm
content: Quote from: PlainName on July 07, 2025, 08:36:14 pmI worked for a company which had a product like that. After a few years the config files had grown so big and convoluted that a change that should have been minor took 6 months.They did that because code changes required heavyweight oversight processes but config changes didn't. Cough spluttter, really?!

author: PlainName
author_rank: Super Contributor
timestamp: July 07, 2025, 09:34:24 pm
content: I did it like that because I didn't know how I would want everything to work together initially, other than to realise however it happened it would want to be changed. In this particular case (it was designed not to be task specific) it rarely changed, but I did need to make a small change last year. Easily done, and the actual config could be easily run up in simulation before embedding it for real.Had it made sense to hard code everything you can bet your ass I would have done so in 1996

author: brucehoult
author_rank: Super Contributor
timestamp: July 08, 2025, 04:14:50 am
content: Quote from: peter-h on July 07, 2025, 06:54:46 pmYou've been able to conveniently do garbage collection in C for at least the 37 years since Boehm GC was first released. I'd been using it myself for nearly 30 years, and used it in commercial production "feature phone" software 20 years ago, and telephone exchange ("IN") software 25 years ago.What you can't do in C is compaction -- copying an in-use object from one place to another.You also in C can not reliably know whether something that looks like a pointer into the heap is actually a pointer or just some random integer or character string etc. This can cause some memory that is actually not referenced to be mistakenly retained but in practice it is almost a complete non-issue, especially on today's 64 bit machines, but also on 32 bit microcontrollers with only a few hundred KB or few MB of RAM starting at some high non-zero address, as they almost always do e.g. 0x80000000 or 0x20000000, let alone ESP32 with internal RAM at 0x3FFE0000 and external RAM at 0x3F800000. You just don't get a lot of random data with values like that.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2025, 06:42:51 am
content: Quote from: tggzzz on July 07, 2025, 09:27:37 pmThere is indeed a delicate balance finding just the correct amount of generalization and configurability. Beginner projects are usually ad hoc spaghetti with little architectural design, where run-time desires escalate to code changes and usually only the sole author is capable of modifying the code to do what is needed, and even that takes a lot of time after a period of not working with the code. On the other hand large team of professionals in corporate or academic setting carry a risk of overdesigning the architecture, adding layers of abstraction and generalizaton of everything into a fully configurable system, which eventually becomes not only bloated and buggy, but so difficult to actually configure that the beginner-level ad-hoc code solution would have been easier.I guess the balance can be found by having suitable team size for the actual complexity of the project (usually a bit too small is better than a bit too large), clear timelines for deliverables and a manager who is interested in the key questions "does this bring value to the end user?" "when this is ready?" - no matter how agonizing we technical folks feel these hard questions are (we would prefer to talk about our elegant design choices, but in 98% cases, it just does not matter).

author: tggzzz
author_rank: Super Contributor
timestamp: July 08, 2025, 08:27:51 am
content: Yes, you can do inaccurately do half of a garbage collector like that.It has always astounded me that is regarded as an advance. Given the many demonstrated alternatives, it has always struck me as a demonstration of limitations.It like someone saying you can cross the Atlantic in a sailing ship by their but only clockwise if you go to ports in the right order.Meanwhile the rest of the world has developed more practical alternatives.Cue people giving the reasons sailing ships are better.

author: brucehoult
author_rank: Super Contributor
timestamp: July 08, 2025, 10:21:24 am
content: Quote from: tggzzz on July 08, 2025, 08:27:51 amOne limitation of this platform is that if there is no quote then there is no way to know what something is a reply to. Or at least no way I'm aware of. So I'm only assuming that's a reply to me.Many many commercial products are based on Boehm GC. It is battle-tested and works extremely well, and the low level details are well enough implemented that it often outperforms more "computer sciencey" academically approved GCs. As well as outperforming most malloc/free libraries too.A lot of projects have used Boehm GC at first for a few years, then replaced it with their own "real" GC and gotten worse results. The only project I know off hand that got a definite improvement was Mono which used Boehm up to version 2.8 (first six years).One prominent app which I'm pretty sure uses Boehm GC to this day is Inkscape.

author: PlainName
author_rank: Super Contributor
timestamp: July 08, 2025, 02:36:19 pm
content: Boehm GC seems to be aimed at a pukka OS running on a proper PC. Could it be used on, say, an ESP32 or similar?

author: peter-h
author_rank: Super Contributor
timestamp: July 08, 2025, 02:39:32 pm
content: QuoteIn the 1980s I used to code in asm for the 8086 and 286Surely that will do remapping with the resolution of the segmentation. So you don't get a heap, with garbage collection / compaction capability, unless you space out the blocks. What I don't remember is the resolution; I always coded in real mode, not protected mode.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2025, 03:32:49 pm
content: Quote from: peter-h on July 08, 2025, 02:39:32 pmThe point is, whatever the block (page) size, it's small compared to available memory. And the OS manages this behind application programmer's back. Therefore, you can just malloc() a gigabyte, and underlying physical memory representation can have gaps. As a result, compaction capability, or support for non-contiguous storage in programming language level, is not nearly as important as it would be if we didn't have the concept of virtual memory paged into physical memory. Interestingly, small MCU systems benefit the most from these concepts, but usually can't take the bloated nature of generic, modern high-level approaches, so instead custom (simple) allocators are written ad hoc.

author: brucehoult
author_rank: Super Contributor
timestamp: July 08, 2025, 03:36:54 pm
content: Quote from: PlainName on July 08, 2025, 02:36:19 pmAs I already explained inhttps://www.eevblog.com/forum/microcontrollers/how-does-microcontroller-keep-track-of-heap/msg5969911/#msg5969911and some other messages in this thread, I used Boehm GC in a highly successful Java to native ARM compiler and runtime for "feature" phones with 390k to 2MB RAM and 1 or 2 MHz ARM7TDMI ... no MMU.Just before iPhone and Android arrived our system was used by 9 of the top 10 mobile games companies (everyone except EA, who were perpetually "evaluating"), plus Microsoft, Google, Yahoo!, Amazon, and Disney for their apps on Qualcomm BREW phones.Several companies did full comparisons of using our compiler -- which took a couple of minutes to convert Java to C++ then machine code -- with teams in India or Russia converting their Java to C++ by hand. I all cases our code ran faster, our binary code was smaller, and our heap usage was less despite us using Boehm GC and the others using malloc/free (new/delete).

author: AVI-crak
author_rank: Regular Contributor
timestamp: July 08, 2025, 03:56:58 pm
content: Option for automatic defragmentation of a fixed-size heap. Use splitting into two streams: place sizes larger than 1/16 of the heap from top to bottom, place everything smaller than 1/16 from bottom to top, monitor for collisions of directions.

author: tggzzz
author_rank: Super Contributor
timestamp: July 08, 2025, 05:24:26 pm
content: Quote from: brucehoult on July 08, 2025, 10:21:24 amFair point about my not quoting, but you got the subject rightMy description of Boehm's GC is based on how he described it in his early papers.Your "computer sciency" phrase implies a surprising defensiveness, and isn't something I would expect from you.My interest is in commercial GCs (and other components and libraries) that are correct. After all, if it is acceptable for me to develop something with known faults, then I can develop it extremely quickly. And LLMs could do the job just as well.That something is "commercial" is insufficient. I have (and I expect you have) seen commercially successful projects that are absolute shit. One of those is, yet again, the first item on the UK news programmes tonight.

author: peter-h
author_rank: Super Contributor
timestamp: July 08, 2025, 06:50:09 pm
content: Which item? I can't see anything obvious

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2025, 06:56:43 pm
content: Quote from: tggzzz on July 08, 2025, 05:24:26 pmOK. Please develop a spacecraft designed to go Mars, but with a known fault it might just go to the Moon instead. I'm holding my breath to see you develop this extremely quickly.

author: tggzzz
author_rank: Super Contributor
timestamp: July 08, 2025, 07:38:03 pm
content: Quote from: Siwastaja on July 08, 2025, 06:56:43 pmI was thinking of software, but in this case the fault is that it only gets 1m off the ground. Job done

author: brucehoult
author_rank: Super Contributor
timestamp: July 09, 2025, 02:48:37 am
content: Quote from: tggzzz on July 08, 2025, 05:24:26 pmSurely everyone is familiar with differences between industry and academia, and in particular a certain kind of academic who builds vast beautiful structures of complexity that don't in the end offer any benefits?Hans Boehm has plenty of academic credentials but he is another kind. Both rigorous and practical. He's worked at Xerox PARC, and has for a decade been researching concurrent programming issues at Google.QuoteAre you implying that Boehm GC has known faults? As in, by design?It does not. It is rugged and reliable. It does what a GC should which is to simulate unlimited memory on a machine with finite memory for programs where the live data is smaller than the physical memory. It does this by discovering objects which are no longer reachable by the program and promotes the re-use of that memory. The #1 thing it must do -- and it is absolutely reliable in this -- is to never reuse memory that is still reachable. As with any GC (and unlike the eager malloc/free) there is no requirement that all unreachable objects are discovered and reused immediately or in any given timeframe.I'm sure you're aware Boehm was involved in developing the memory model for Java, and instrumental in forcing the C/C++ community to address the issue and define the semantics of shared variables. Along the way he developed libatomic_ops. He's worked on fast locks and lock-free algorithms.This is not a guy who is blasé about correctness. On the contrary, he has long been one of the world's leading researchers into memory management and concurrency.I have worked on GC implementations in a number of widely-used systems, including Android Dalvik and ART, Mono, Microsoft CoreCLR (all of those while working at Samsung R&D). I've also used, and contributed to Boehm GC.When I say that in practice the more complex (and hairy!) theoretically better GCs offer little or no actual advantage over Boehm, this is not some idle claim, but the result of years of professional experience.But, hey, if you want to employ me to work on one of those "better" GCs then I'm more than happy to take your money, and I'll do a good job on it too.

author: tggzzz
author_rank: Super Contributor
timestamp: July 09, 2025, 07:57:08 am
content: I have a great deal of respect for Boehm, for the reasons you mention.His C GC does not, as I know you are aware, collect all free memory, merely an undeterminable proportion of it.

author: brucehoult
author_rank: Super Contributor
timestamp: July 09, 2025, 09:37:35 am
content: Quote from: tggzzz on July 09, 2025, 07:57:08 amCorrectness does not require that every unreachable object is collected instantly, and no GC does. There are excellent performance reasons to, for example, not sweep a memory page for unreachable objects until the instant that a new object is about to be allocated from it. Unless you have a very small program eager sweeping results in every memory page in turn being paged from disk in to physical memory, to L3 cache, to L2 cache, to L1 cache in order to be inspected. Doing this incrementally, at the point the page is about to be touched anyway, is much more efficient, but results in objects that have not been marked not being discovered immediately. The same argument applies whether the mark bits are stored in each object or in a separate parallel structure -- the latter of course delays the effect by whatever the ratio of the size of object to size of mark bits is.Boehm has published about the bounds on the amount of temporarily uncollected objects as a result of conservative collection, and the collector allows you to choose whether heap objects themselves are scanned conservatively or precisely (if there is object layout information available). The registers and stack are always scanned conservatively.

author: tggzzz
author_rank: Super Contributor
timestamp: July 09, 2025, 10:04:52 am
content: It is indeed true that Boehm defines that his GC does not do several things found in other general purpose GCs. To that limited extent his half a garbage collector is correctNonetheless, it greatly limits the ability to rapidly and simply assemble a large application from typical libraries which do not have easily usable (or even defined!) memory usage.Analogies are dangerous, but neither do I accept Elon Musk's contention that Tesla full self driving mode is safe because the Ts&Cs state the driver is in control and responsible.

author: tggzzz
author_rank: Super Contributor
timestamp: July 09, 2025, 10:35:15 am
content: To what extent does Boehm's GC work with multi threaded multi core applications?Ditto a typical RTOS, in particular those with separate stacks for each thread?That's something I never had to consider, but it can't be easily dismissed nowadays.

author: brucehoult
author_rank: Super Contributor
timestamp: July 09, 2025, 10:50:06 am
content: Quote from: tggzzz on July 09, 2025, 10:35:15 amOh come on! Boehm GC has been thread and multi-core aware and optimised for decades already.1988: first published versionMid-1990s (Version 3.x): Basic thread support introduced, with conservative stack scanning for POSIX and other threading models, mutators halted during marking using platform-specific mechanisms.2000–2002 (Version 5.x): Multi-core awareness improved with optimized spin-locks and better scalability for multi-processor systems.2003 (Version 6.0): Parallel (multi-core) marking. Thread-local allocation with each thread allocating from its own set of per-size pools (pages) and per-thread free lists.I'm fine with criticism from people who are actually familiar with something, but not with criticism from a position of abject ignorance.I am a professional GC developer, I've been hired AS A GC SPECIALIST by multiple companies including a 4 person startup and Samsung. I've worked on the internals of multiple different and important GCs and I am telling you: Boehm GC is one of the most mature, reliable, high performance GCs out there. To think otherwise is pure ignorance and prejudice.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 09, 2025, 11:29:30 am
content: Quote from: brucehoult on July 09, 2025, 10:50:06 amWith tggzzz, you can count on having endless discussions about "problems" invented in tggzzz's bubble. It is obvious he has not really participated in any real-world software projects, small or large, for at least 30 years, if ever. This kind of theoretical pedantism is of course not unheard of, but I enjoy how his pet projects get a totally free pass, even when they have glaring holes; typical example would be preferring completely unspecified programming languages which arbitrarily change with every version over standardized languages with a few specified undefined constructs, because thedocumentedundefined constructs simply are unacceptable.tggzzz's problem with Boehm GC is that it fights against tggzzz's perception that in C one cannot have garbage collection. When reality and tggzzz disagree, tggzzz wins and reality loses. tggzzzstill thinksthat even C threads are iffy and buggy and quotes random stuff from 1990's, despite C compilers having thread support for over 20 years now, solving the very problem mentioned in those quotes ("threads cannot be implemented as libraries").It is just a one-man crusade against C, and spills over to innocent bystanders like Boehm. This crusade is powered by seriously outdated or plain wrong arguments, which are corrected by several forum members year after year, but nonetheless the discussion is never-ending, because tggzzz never corrects his understanding; after all, it's the real world which is wrong.

author: tggzzz
author_rank: Super Contributor
timestamp: July 09, 2025, 04:55:25 pm
content: OK, let's give you a partial CV, so you can begin to understand how wide of the mark your presumptions are.I started writing multithreaded code during a vacation job, and worked out I needed semaphores. They had been invented less than a decade earlier, and universities regarded them as advanced. My supervisor was surprised I knew what they were, and realised that they would be necessary for my final year project.A few years later I was an early adopter of C in a hard realtime life critical project. Life critical=>someone could die even if it worked as design and to specification. Early adopter=>there were only two books on C. Embedded, multithreaded, with cooperating threads, similar to continuations.Simultaneously I was creating hardware where one mistake cost 3-6 months and several years salary.Hence by the mid 80s, correctly functioning embedded systems in C were already at the forefront of my professional mind.Mid to late 80s I also became aware of how much easier and faster it was to develop and integrate much larger and more complex applications, iff the language supported careful structuring and a full GC. But at that time all GCs were stop the world, so irrelevant for embedded systems.By the mid 90s I had watched a multi-year project founder because it spent all its time fighting C++ rather than the problem domain. That problem would not have existed if they had used one of the more capable existing languages.Simultaneously the C++ committee spectacularly demonstrated they did not understand the consequences of their kitchen sink creation. It took Erwin Unruh to rub their noses in it.Simultaneously there was a year long committee debate about whether it should be possible or impossible to cast away constness.All that time I had been using C, for less and less, as I concluded that C and C++ were out of control, and even their committees were struggling. In that case ordinary developers didn't stand a chance.That was confirmed by Nick MacLaren producing his "objects diatribe", since his HPC consulting experience meant he knew where the landmines were buried, and consequently many compiler implementers asked him what the standards meant. Ridiculous; the standards should be clear and understandable - but weren't.Consequently I decided that C and C++ were a waste of my life. My professional interests moved to embedded soft real-time telecom systems, and I implemented several in a language which has good typing and a good GC.Later still, I was gobsmacked by finding Boehm needed to write his threads manifesto; why on earth was that necessary? The only answer I could think of was that expert C/C++ developers didn't actually understand the fundamentals of the language; maybe they were so busy fighting undergrowth that they had no time for the trees and had forgotten forests existed.Summary of C and C++ for the last quarter century: academics putting lipstick on a pig. Fortunately, finally better alternatives are emerging, albeit slowly. Everybody except old farts will move on, just as they have from COBOL.So forgive me if I decided that C and C++ would inhibit my ability to create valuable real-time systems. Fortunately I was able to find employers who agreed with me and valued (financially and otherwise) my experience and wisdom.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 09, 2025, 06:27:58 pm
content: Quote from: tggzzz on July 09, 2025, 04:55:25 pmI think you pretty much confirmed my observation - your list stops to approx 30 years ago, which I mentioned. (I will apologize for the "if ever" part - that was uncalled for.)I think your mistake was locking into your conclusions you made circa 1995 or so. The world did move forward - C++ of course continued to get more bloat, but on the other hand, C got memory model you needed, C got the real thread support from compilers you needed. Your observations have been correct, but you miss the fact others did those observations too, and things have been getting fixes. This is incremental development, which in real life seems to work out better than revolutionary new solutions - although sometimes revolutions are needed too.Because the world is not fixed, you need to keep observing and update your knowledge.

author: peter-h
author_rank: Super Contributor
timestamp: July 10, 2025, 03:47:33 pm
content: QuoteDo you have more detail on that?AIUI GCC does not use the heap in the codeit generates. The heap, and static variables and whatever other RTOS-problematic stuff is not used in a hidden manner by the compiler.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 10, 2025, 04:34:16 pm
content: C11 introduces direct and platform-agnostic thread support, although this is *optional* and in practice only available on hosted platforms, don't expect to get anything like this for microcontrollers. It was heavily inspired by PThreads and is only a thin layer over it on Posix-compliant platorms, but it has the merit of being part of the C standard and thus, portable (as long as it's supported...)I don't know if Siwastaja had anything else in mind.A C compiler per se will never use the heap if not explicitely used. Of course, the caveat is that some functions of the standard library may use the heap (such as printf functions) and many people consider that part of the compiler, so that's where things can get blurry.The compiler itself will never use the heap behind your back, but code in linked libraries could.

author: peter-h
author_rank: Super Contributor
timestamp: July 10, 2025, 04:57:17 pm
content: The compiler should also never use static variables. Everything should be on the stack. And I've seen nothing to the contrary in GCC for arm32.As you say, libs are another thing, but even there it seems to be limited to the printf/scanf/heap family. I don't think any other functions do anything naughty.It's actually an interesting Q why this should be (just the printf/scanf/heap). Probably that is what has grown into general acceptance over many years. But also PC programmers don't care anyway about heap usage, and AIUI PC software doesn't have to be re-entrant (Windows gives each program its own RAM; this is not the 1980s "Concurrent DOS" model where code had to be truly re-entrant since only one copy existed in RAM, and mainframe multiuser programs often used indexed data structures where each time you called a piece of code you supplied the user ID). It is only in embedded / RTOS applications where programmers are bothered about this.I recall many years ago some C libs having weird extra functions. For example a Watcom 80x86 compiler (which to my amazement still exists) had a lib function which generated a directory listing, presented as a linked list. This greatly eased writing PC software which had to present a tree file listing (otherwise you had to use the search first / search next method). But I can see that could have used the heap.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2025, 05:20:28 pm
content: Quote from: Siwastaja on July 09, 2025, 06:27:58 pmISTR you started c2000. In that case you will have missed the nonsense of the 1990s which confirmed that experts did not understand the language they were creating, and hence were constructing castles on sand. That was reiterated by Boehm's report.That was demonstrated in Nick MacLarens objects diatribe where the committees could not answer basic questions about their creation. Note that MacLarens questions derived from the problems he had had to diagnose in user programs. He did write a series of guides about which bits of C should be avoided; that should not have been necessary.In the 1990s C faced a choice: to become a general purpose application language or to remain a close to the silicon systems programming language. Either choice was possible, and either would have been good. But the committee never resolved that dilemma, choosing to sit on the wall, and to throw in the kitchen sink.Consequently the language is unnecessarily complicated and many people have problems using it - even those who think they are good or expert C programmers (e.g. hence Boehm's threads paper).One manifestation of that is endless complaints about compiler faults, followed by people telling them they unwittingly used UB or IB - and it is far too easy to do thatOverall the language has become so complex and with subtle unexpected behaviour that developers spent too much energy fighting the language and to little working on the application. The language has become part of the problem, when it should be part of the solution.Fundamentally any language that needs to be subsetted for a given project is waving a big red warning flag.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2025, 05:30:12 pm
content: Quote from: SiliconWizard on July 10, 2025, 04:34:16 pmSo C has mutated away from being a language to being a family of related languages?Not good, and likely to lead to late surprises in a project.QuoteSuch blurryness is not good!If a library's invisible internal working can bugger your application, thats not good. I remember that happening

author: peter-h
author_rank: Super Contributor
timestamp: July 10, 2025, 06:40:39 pm
content: It is however what we seem to have had since newlib printf etc. Check the other thread on newlib printf and the heap etc. That thread contains the source code. From my project notes:The likely original printf source in libc.a washttps://sourceware.org/git?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdio/vfprintf.c;h=6a198e2c657e8cf44b720c8bec76b1121921a42d;hb=HEAD#l866Probable scanf sourcesThe likely scanf source in libc.a (which is still used in the product) ishttps://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdio/sprintf.c;h=be66ec6f5d73589e5147afeb7634b9de0daba09d;hb=HEADhttps://sourceware.org/git/?p=newlib-cygwin.git;a=tree;f=newlib/libc/stdio;h=0f5e4dd0dc465029d0b6c0a5d03fc2cc70e8df87;hb=HEADand that does use malloc but it’s not clear if the one in the Cube lib uses it. A breakpoint on _sbrk is not hit.Other Newlib sources can be found herehttps://developer.arm.com/downloads/-/arm-gnu-toolchain-downloadsProbable heap sourcesIf looking for heap source code, this may help:https://www.eevblog.com/forum/programming/heap-analysis-tool-for-embedded-systems/Note that our frozen libc.a contains the heap code also.The current heap code seems to allocate 4 bytes for each block, and sometimes 4 more bytes are added for alignment. See herehttps://www.eevblog.com/forum/programming/heap-analysis-tool-for-embedded-systems/msg4835657/#msg4835657However the suggested candidate source codehttps://github.com/devkitPro/newlib/blob/master/newlib/libc/stdlib/_mallocr.cappears to not be the one (unless fixed since) because the problem described herehttps://stackoverflow.com/questions/39088598/malloc-in-newlib-does-it-waste-memory-after-one-big-failure-allocation/76138157#76138157(which does refer to the above source) is not present in ours. A malloc of a block larger than heap space fails correctly but subsequent reduced mallocs work fine.A more likely candidate sourcecode is the “nano”https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/nano-mallocr.c;h=13b72c99ffd7007b53c2e3270a56da237857742a;hb=HEADbut it probably is not, or not quitehttps://www.eevblog.com/forum/programming/help-needed-with-some-heap-test-code/msg4845821/#msg4845821Both above sources have a comment:The minimum allocatable size is 16 bytes. Even a request for zero bytes (i.e., malloc(0)) returns a pointer to something of the minimum allocatable size.and this has been verified for ours.This summarises the likely sourceshttps://www.eevblog.com/forum/programming/heap-analysis-tool-for-embedded-systems/msg4844564/#msg4844564Our heap (whose-ever it is) has a bug:https://www.eevblog.com/forum/programming/help-needed-with-some-heap-test-code/msg4838741/#msg4838741This all seems to have been common knowledge since the 1990s - newlib printf etc uses the heap. ISTM that anybody working in embedded stuff was expected to have somehow known this. I obviously didn't until I was told about it on EEVBLOG

author: gf
author_rank: Super Contributor
timestamp: July 10, 2025, 06:54:28 pm
content: Quote from: tggzzz on July 10, 2025, 05:30:12 pmMalloc() is provided as part of the C library. Why do you consider it a problem if other components of the C library use it?Why would that necessarily bugger your application?The documentation also doesnotsay that printf() cannot fail. I can fail, returning a negative value.The problem is rather that nobody checks the return code.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2025, 07:34:38 pm
content: Quote from: gf on July 10, 2025, 06:54:28 pmIf no problem arises (from using malloc etc) then no problem arises.The problem is that in practice people cannot tellin advancewhether there will be a problem. Late surprises are always unpleasant, sometimes worse.Humans being what they are, it is, reassuring if you areforcedto recognise failure mechanisms, and take appropriate action. However, if the possibility of a failure isexplicitlyvisibleexternally, then it is not unreasonable to castigate someone who didn't bother to check.

author: gerbay
author_rank: Regular Contributor
timestamp: July 10, 2025, 07:35:06 pm
content: Quote from: gf on July 10, 2025, 06:54:28 pmThe C library doesn't know whether you've allocated heap space. Even if you have, it doesn't know the amount. Different projects are linked with the same standard/runtime library. Different projects' heaps may have different sizes, or they may not have any allocated heap space at all. Therefore, you need to be well-versed in the needs and behaviors of the libraries you use. You should also take this into account in your own code.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2025, 07:41:22 pm
content: Quote from: gerbay on July 10, 2025, 07:35:06 pmEven where practical, that is a high cognitive load. I prefer to expend my limited brainpower on the application, not the tool.

author: gerbay
author_rank: Regular Contributor
timestamp: July 10, 2025, 07:54:01 pm
content: Since there aren't many such situations, I don't think it would add much cognitive load. There are also libraries that don't require heap space. Once you've prepared your environment, you can use the same environment in all your projects.The problem isn't just about the heap. For example, a variable you declared as a local variable in a function might require too much stack space. When this function is called, data is randomly overwritten due to insufficient stack space. Because your data is corrupted, unrelated functions will appear to be malfunctioning.

author: gf
author_rank: Super Contributor
timestamp: July 10, 2025, 08:47:44 pm
content: Quote from: gerbay on July 10, 2025, 07:35:06 pmIt's rarely a problem on desktop or servers systems where you can allocate (say) 1GB dynamically and everything is still fine (unless your program has a memory leak and grows continuously, or unless the program really needs ahugeamount of memory). But I agree that you have to be very careful when you compile/link microcontroller software/firmware, where you have to allocate stack/heap statically at link time and where the available amount of RAM is sometimes very limited.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2025, 08:58:49 pm
content: Quote from: gerbay on July 10, 2025, 07:54:01 pmThat presumes one project is similar to the last. That has not been the case for me.Arguably it is easier to spot/predict too much stack space being used early in a project than it is to spot/predict heap problems. Recursive subroutine calls present a problem, though.

author: John Coloccia
author_rank: Super Contributor
timestamp: July 10, 2025, 11:55:34 pm
content: The general rule on embedded projects, and certainly safety critical, is you do not dynamically allocate memory. And it's acceptable sometimes during initialization, but it's just as easy to statically allocate the memory you need. The advantage here is you know at compile time that you've screwed up.But this isn't a C thing. It's unhelpful for a Rust or ADA project to run out of memory too.Sheesh. It's like we're deevolving. A thing in C that's appropriate on a huge server somehow also needs to be appropriate on a tiny little micro.

author: Smokey
author_rank: Super Contributor
timestamp: July 11, 2025, 12:05:09 am
content: Quote from: mikeselectricstuff on July 04, 2025, 09:49:13 amThis is the truth.

author: radiolistener
author_rank: Super Contributor
timestamp: July 11, 2025, 12:28:50 am
content: The MCU itself has no awareness of heap memory allocation, functions like malloc/free are part of the standard C library and their implementation varies depending on the library used.However, dynamic memory allocation on MCU is generally considered a poor practice. Due to the very limited RAM available, embedded code on MCU typically relies on static memory allocation. This approach avoids potential issues such as unexpected out-of-memory conditions and memory fragmentation - both of which are especially critical in resource-constrained environments, where even small inefficiencies can lead to system instability.

author: westfw
author_rank: Super Contributor
timestamp: July 11, 2025, 12:45:41 am
content: Quote"I know! We can fix C's problems by defining a new language that is EVEN MORE COMPLEX."Sigh. I really liked that C, the basic language, was entirely separate from any of the libraries that were typically used with it.This made it extra-ordinarily easy to port to new platforms. Sometimes (post-posix), you'd eventually implement standard libraries as well, other times you'd do something else, and it would still be OK for the (usually) quirky little microcontroller that was the target. (Rather like Arduino, that almost gets away with never using libc. (Although it's nice for most of us that avr-libc is there.)Unfortunately (IMO), things have gotten fuzzy recently, and a compiler will look at your code and replace pieces of it with calls to standard libraries instead, under the auspices of "optimization." Grr. (eg replacing byte copy loops with calls to memcpy())The "the standard says this is undefined behavior, so we don't have to implement it in the obvious way that you're used to. (also frequently because "optimization.") (eg "type punning.")

author: brucehoult
author_rank: Super Contributor
timestamp: July 11, 2025, 02:03:01 am
content: Quote from: tggzzz on July 10, 2025, 08:58:49 pmHow so?Obviously, no one should use recursion without knowing the bounds on it.Many recursive functions such as walking a balanced binary tree, or a sensible Quicksort that recurses on the smaller partition then tail calls / iterates on the larger one, are inherently limited to recursing approximately log2(N) deep where N is the number of objects in the tree or array, which is inherently limited by the size of addresses on your machine. i.e. on a 32 bit machine no sensible recursive algorithm will go more than 32 levels deep, which for most functions will use 1 KB of stack space, or maybe 2 KB. Multiply that by four on a 64 bit machine -- 2x for the greater possible recursion depth, and 2x for the pointers and registers being twice bigger.This is absolutely trivial on Linux with by default I think 10 MB of stack per thread, or even Windows with 1 MB per thread.Besides which, a recursive algorithm with 2-way recursion in each function will quickly use a large amount of time -- this is a far bigger problem than stack usage. Ok, on a 32 bit machine with a reasonably simple function you can actually do 4 billion function calls and returns in a reasonable amount of time, possibly even just a few seconds to a minute or two. Try that on a 64 bit machine and 2^64 function calls will take more than a century even at 1ns per call.So, no, recursion isn't going to run you out of stack space on any reasonable algorithm.Running out of stack space is usually people allocating large arrays on the stack instead of in the heap where they belong. If you're going to actually touch all the elements of that 1 MB array then the processing is going to take far more time than calling malloc() and free() instead of bumping your stack pointer.

author: John Coloccia
author_rank: Super Contributor
timestamp: July 11, 2025, 02:27:37 am
content: Ultimately, it starts at knowing what your worst case is, and defining what you do then.So even if I have a worst case based on some random load, say a communication protocol where I don't know what my ultimate load is, you need to cut it off where you can handle it, and define what hapoens when you can't.Maybe you just ignore the load and timeout. Maybe you recognize it's too much and respond with an error.It's all cool. You just need to define it, and then the rest of the world knows what to expect.I think too many engineers want to build rolls royce systems that handle everything. The path to goodness is to define your limits, but then also define something graceful when they're exceeded.And then you can guarentee that your thing always does something reasonable.Error checking is key, but what is often missing is doing something well defined and sensible with the error. But this is really where the rubber hits the road. Does no good to check for null, for example, if you don't do something sensible when it happens. Just silently failing is no better than flying off into space.

author: brucehoult
author_rank: Super Contributor
timestamp: July 11, 2025, 02:34:18 am
content: Quote from: westfw on July 11, 2025, 12:45:41 amThe number of such cases is very limited. Pretty much just memcpy() and memset() I think, and it only happens when it makes sense e.g. for a size that isn't known at compile time (or exceeds some limit, such as 16 bytes). If you provide your own memcpy function -- which you can make a simple byte copy loop if small size is what you most want -- and then the linker will use that instead of the bloated unrolled libc one.What other cases are there? Replacing a printf() of just a format string with puts() is common. Any others?No a big deal, and helpful compiler is, on the whole, actually helpful.If you're really serious about getting exactly the machine code that you want then write that bit in assembly language not C.

author: tggzzz
author_rank: Super Contributor
timestamp: July 11, 2025, 02:40:30 am
content: Quote from: John Coloccia on July 10, 2025, 11:55:34 pmYes.It is one of the unintended consequences of C plus libraries attempting to be all things to all people. That dilemma came to the fore in the 1990s.

author: tggzzz
author_rank: Super Contributor
timestamp: July 11, 2025, 02:42:58 am
content: Quote from: westfw on July 11, 2025, 12:45:41 amYup.

author: John Coloccia
author_rank: Super Contributor
timestamp: July 11, 2025, 02:50:43 am
content: Quote from: tggzzz on July 11, 2025, 02:40:30 amI disagree. It's just knowing your tools. As a pro, you are expected to know your tools. So you don't shove an Allen key into a torx bolt, even though you can with some effort and a hammer.I think you're arguing that as a whole, we've done a poor job of educating how to use the tools. And I get that and agree, but I also see thow complex things are and understand that you simply can't teach everything to everyone. Some stuff you just have to absorb, and hopefully you're either brilliant or are surrounded with reasonably experienced guys that can show the way.But that doesn't make the tool bad. Nor does it make other tools better.Part of being a pro is choosing the right tool for the job, taking everything into account, and then setting standards and patterns to make the tool as easy to use as possible.

author: tggzzz
author_rank: Super Contributor
timestamp: July 11, 2025, 02:58:28 am
content: Quote from: brucehoult on July 11, 2025, 02:03:01 amWe agree, with the caveat that very limited stack space is likely on embedded systems, especially those with multiple execution contexts (e.g. threads) each with their own stack.That was my starting point, many decades ago - and still one of interest to me today.

author: tggzzz
author_rank: Super Contributor
timestamp: July 11, 2025, 03:07:54 am
content: Quote from: John Coloccia on July 11, 2025, 02:50:43 amYes and no.I'm arguing that the tool has become so complex that very few people can understand it.That was demonstrably the case in the 1990s,when even the standards committee was having severe problems.Part of that was the desire for C to be all an Allen/torx/screw/nail, part the requirement to not break existing code. Those problems haven't changed, and people haven't got cleverer, so ...

author: John Coloccia
author_rank: Super Contributor
timestamp: July 11, 2025, 03:29:50 am
content: Quote from: tggzzz on July 11, 2025, 03:07:54 amBut it's not the 90s. Casting rules are set in stone. Standards like MISRA are pretty well accepted and give good advice. The entire point of C is that it's a very good abstraction of a very simple general purpose processor that pretty much everything can implement. It's better thought of as a high level processor abstraction than a programming language.And it's up to you how you use it. If C ain't your thing, then don't use it. I rarely use C other than embedded software, and most often that's safety critical software. And that means I do a LOT of C, and it's a very warm, fuzzy place for us, absent of all the ridiculous pointer problems and other nonsense that seems to plague everyone else. Good standards and good patterns.Ohhhh, just use Rust. Or SparkADA or whatever. Great. Where's all the tooling for unit testing and other things that actual safety critical software is kind of expected to have?But you think C#...or Rust....or anything else makes anything better? You just have different bugs to deal with, so you have to put different standards in place to deal with them.Look, I'm not going to pretend that we've done, or do, a good job of training software engineers. I think our training is pitiful, and frankly it was pitiful as an undergrad in the late 80's and early 90's. I finally learned something when I had to do something real in grad school, and honestly I only really learned anything useful when I got my first job and real engineers showed me the way. So I get what you're saying.But it's just a complicated thing if you're not playing in some web sandbox and if you're doing anything that's remotely close to hardware. It's just the nature of the beast.

author: gf
author_rank: Super Contributor
timestamp: July 11, 2025, 04:52:20 am
content: Quote from: brucehoult on July 11, 2025, 02:34:18 amThere is still the -ffreestanding option of gcc which should generate code that does not rely on libc.

author: brucehoult
author_rank: Super Contributor
timestamp: July 11, 2025, 05:06:10 am
content: Quote from: gf on July 11, 2025, 04:52:20 amEven with -ffreestanding gcc is still free to generate calls to functions such asmemcpy,memset, or arithmetic helpers such as__divdi3for division. These are not libc functions but compiler intrinsics or libgcc functions, which are part of GCC’s runtime support. To avoid these, you may need additional flags like-fno-builtin.

author: gf
author_rank: Super Contributor
timestamp: July 11, 2025, 06:16:43 am
content: Quote from: brucehoult on July 11, 2025, 05:06:10 am-ffreestanding implies -fno-builtin.But apparently, both still don't prevent gcc from calling memcpy for assignment of a large struct.In the meantime I found the following explicitly mentioned in the gcc docs:Quotehttps://gcc.gnu.org/onlinedocs/gcc/Standards.html

author: brucehoult
author_rank: Super Contributor
timestamp: July 11, 2025, 08:37:59 am
content: Quote from: gf on July 11, 2025, 06:16:43 amHmmm. "Contrary to the standards coveringmemcpyGCC expects the case of an exact overlap of source and destination to work and not invoke undefined behavior"By which they meandst==src? That should work naturally in simple implementations, but if it happens often then I guess you can just add "beq dst,src,return" to optimise it, along of course with "beqz n,return".I wonder whether gcc requires you to return the original value ofdst, and the standardmemcpydoes. That generally adds one instruction :-)Assuming the concern is size rather than speed (you don't want to drag in an 800 byte implementation ofmemcpyfrom libc), if only takes a couple of dozen instructions to implement all four routines.I'm usually more annoyed at gcc NOT callingmemcpybut using a completely unrolled copy of individual bytes when I told it to usememcpy.https://godbolt.org/z/cbc8sW4M9

author: hans
author_rank: Super Contributor
timestamp: July 11, 2025, 08:39:07 am
content: Quote from: John Coloccia on July 11, 2025, 03:29:50 amSide tangent.. anecdotally my time spent debugging my own code went down drastically as soon I started bumping it up to C++.RAII is quite a powerful tool, also for unit testing. Unlike some I really like C++'s stricter type system. It forces better patterns, even though I can still type reinterpret_cast to get the same pointer casting shenanigans in C. Its usually indication of a code smell that needs attention. Sometimes a small amount of templating can fix the problem.C# also allows "unsafe" code, and iirc Rust has similar facilities in place with their borrow checker, but I think its good these protections are in place.You don't need them to get the job done though. Especially when you need to rely more on standards like MISRA, static analyzers, etc., then I acknowledge there is a huge gap. C++ could even be a major complication not worth going through.

author: gf
author_rank: Super Contributor
timestamp: July 11, 2025, 09:36:10 am
content: Quote from: brucehoult on July 11, 2025, 08:37:59 amIf code size matters, GCC apparently emits a tail call to memcpy when -Os is used.The unrolled inline code has, of course, no call overhead, and is branch-free, avoiding branch misprediction issues.Another drawback of calling an external memcpy function is that it is not possible to optimize for a constant length at compile time. Instead, conditional branches are required inside memcpy to distinguish between different cases of the implementations. The same applies to the alignment of the source and destination. In many cases, the alignment is known at compile time by the caller of memcpy. However, if memcpy wants to use different code for different alignments, it can only decide dynamically at runtime. Again, any conditinoal branch can be mispredicted, penalizing performance.

author: tggzzz
author_rank: Super Contributor
timestamp: July 11, 2025, 09:44:56 am
content: Quote from: John Coloccia on July 11, 2025, 03:29:50 amMISRA is not, of course, C. It is a neutered subset of C. C zealots claim the non-neutered power of C is compelling, in which case MISRA cannot be an argument. For the avoidance of doubt, I like the objectives and feel of MISRA.Ada and Spark developers will claim that if it compiles it is likely to work. While I'm sure that's an exaggeration, I suspect there is a grain of truth. Potentially useful, but hardly compelling.Tooling is an important issue, but there are languages with much better tooling support than C can ever have - due to fundamental aspects of C.I'm sure const casting is nailed down. I used it as an example of the dilemmas faced by the standards committees.I doubt all of the issues raised in MacLarens object diatribe have been nailed down; I expect it will be a case for most developers of "operational semantics", i.e. suck it and see.Please explainwhyBoehm felt it necessary to write his threads paper. Clearly it was aimed at experts, and probably committees.Please explainwhy, if the standards committee is so expert, they didn't feel it necessary to have a memory model for a quarter of a century after it was obvious to other language developers with a wider experience.

author: brucehoult
author_rank: Super Contributor
timestamp: July 11, 2025, 10:23:31 am
content: Quote from: gf on July 11, 2025, 09:36:10 amMaybe you didn't follow the godbolt link, above, but it shows calling libcmemcpywith a constant length and gcc replaces the call tomemcpywith inline straight line unrolled code.This will happen even if you're using your own memcpy at link time, because gcc has no way of knowing you're going to do that. So, for example, you can't do logging "Memcpy called with src=... dst=... n=..."

author: radiolistener
author_rank: Super Contributor
timestamp: July 11, 2025, 08:17:06 pm
content: Quote from: hans on July 11, 2025, 08:39:07 amyes, the safety checks in C# (and similar mechanisms in Rust) are very helpful for catching out-of-bounds errors early and easy. Certainly, you can use something like.net nanoframeworkfor MCU, however such languages rely heavily on heap allocation, which makes them less suitable for MCU where memory size is extremely limited. On MCU, static or stack allocation is typically preferred to avoid issues like fragmentation and unpredictable memory usage.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 11, 2025, 08:58:05 pm
content: Quote from: brucehoult on July 11, 2025, 10:23:31 amNote that if using the '-ffreestanding' compilation flag, it will emit a call to memcpy at any optimization level. The linking pass will (or at least should) fail as it won't link with the standard lib, so memcpy will be undefined.You can't directly use the equivalent builtin function in freestanding mode as far as I've tested (but I may have missed something) even when it could be inlined code as with your example - probably out if simplicity and consistency.The compiler assumes that there is no access to the std library in freestanding mode, so, no memcpy for you. Not even when it could be inlined.

author: gf
author_rank: Super Contributor
timestamp: July 11, 2025, 09:35:51 pm
content: Quote from: brucehoult on July 11, 2025, 10:23:31 amI think you misunderstand me. Had I not followed the Godbolt link, I would not have responded with: 'The unrolled inline code has, of course, no call overhead and is branch-free, thus avoiding branch misprediction issues.'OTOH, I just wanted to point out some drawbacks of emitting a call to memcpy instead of emitting inline code. The former happens, for example, when you replace -O with -Os in your Godbolt example, or if you add -fno-builtin-memcpy. What I wanted to say is, if a memcpy implementaion wants to distinguish different cases (e.g. byte-wise copy versus word-wise copy), it can only make this decision at runtime, while inline code can make some of these decisions at compile time, omitting the comparison instructions for constant conditions and omitting the dead code for the unreachable branches.I also don't recall saying that the compiler would generate different code if I provided my own memcpy at link time. Of course it cannot know that in advance.Quote from: SiliconWizard on July 11, 2025, 08:58:05 pmAs bruceholt pointed out earlier, it's actually -fno-builtin, which prevents memcpy() from being replaced by __builtin_memcpy(). Whether __builtin_memcpy emits inline code or a call to memcpy depends on the optimization level1). Since -ffreestanding also implies -fno-builtin, the same applies to -ffreestanding.1)Edit: I think it's not only a matter of optimization level. __builtin_memcpy may generally fall back to emitting a call to memcpy in some cases, for example if the inline code would become too complex or too big.QuoteNot sure what you mean exactly. Your program can call __builtin_memcpy() in freestanding mode, and GCC emits either inline code or a call to memcpy, depending on optimization level. Seehttps://godbolt.org/z/na18z77r3

author: PlainName
author_rank: Super Contributor
timestamp: July 11, 2025, 10:18:40 pm
content: QuoteI would have thought stack allocation would be worse than dynamic allocation, since there is no way to return a fail due to lack of memory.

author: John Coloccia
author_rank: Super Contributor
timestamp: July 11, 2025, 11:53:59 pm
content: Quote from: PlainName on July 11, 2025, 10:18:40 pmThe stack doesn't fragment. Also, you can determine max stack required as long as you can determine your max recursion depth, assuming you're recursing for some reason.The idea is that if you understand your problem well, and define your limits, it's basically impossible to run out of memory at runtime. Part of that strategy is statically defined memory, and an upper bound on stack usage.And there are clever ways of estimating your max stack usage.