Forums

No dynamic memory at all: newlib and sprintf

Started by pozz August 30, 2019
I'd like to avoid using dynamic memory (heap) at all. I'm using gcc for 
ARM and newlib (really I'm using NXP MCUXpresso IDE, but I don't think 
this is important).

When the code is minimal, I noticed heap related functions (malloc, free 
and so on) aren't included in the output binary file.
However, as soon as I use sprintf-like functions, heap is used.

I looked at the newlib source code and indeed I noticed __ssputs_r(), a 
common shared function between sprintf-like functions, that uses malloc.
I think it uses malloc for asprintf-like functions that I'm not using.

How to remove completely the heap with sprintf functions? Maybe by 
implementing a custom minimal sprintf function?
On 30/08/2019 09:29, pozz wrote:
> I'd like to avoid using dynamic memory (heap) at all. I'm using gcc for > ARM and newlib (really I'm using NXP MCUXpresso IDE, but I don't think > this is important). > > When the code is minimal, I noticed heap related functions (malloc, free > and so on) aren't included in the output binary file. > However, as soon as I use sprintf-like functions, heap is used. > > I looked at the newlib source code and indeed I noticed __ssputs_r(), a > common shared function between sprintf-like functions, that uses malloc. > I think it uses malloc for asprintf-like functions that I'm not using. > > How to remove completely the heap with sprintf functions? Maybe by > implementing a custom minimal sprintf function?
Check how __ssputs_r is using the heap. Maybe you can get away with just adding these to your code somewhere: static char heap[100]; void * malloc(size_t size) { (void) size; return heap; } void free(void *ptr) { (void) ptr; } If the __ssputs_r function only ever calls malloc once while it works, and you don't use it from different threads or contexts (the "_r" in newlib is for reentrant code), that might do the job, while saving you from the effort of making your own printf stuff.
Il 30/08/2019 09:57, David Brown ha scritto:
> On 30/08/2019 09:29, pozz wrote: >> I'd like to avoid using dynamic memory (heap) at all. I'm using gcc for >> ARM and newlib (really I'm using NXP MCUXpresso IDE, but I don't think >> this is important). >> >> When the code is minimal, I noticed heap related functions (malloc, free >> and so on) aren't included in the output binary file. >> However, as soon as I use sprintf-like functions, heap is used. >> >> I looked at the newlib source code and indeed I noticed __ssputs_r(), a >> common shared function between sprintf-like functions, that uses malloc. >> I think it uses malloc for asprintf-like functions that I'm not using. >> >> How to remove completely the heap with sprintf functions? Maybe by >> implementing a custom minimal sprintf function? > > Check how __ssputs_r is using the heap. Maybe you can get away with > just adding these to your code somewhere: > > static char heap[100]; > void * malloc(size_t size) { > (void) size; > return heap; > } > > void free(void *ptr) { > (void) ptr; > } > > If the __ssputs_r function only ever calls malloc once while it works, > and you don't use it from different threads or contexts (the "_r" in > newlib is for reentrant code), that might do the job, while saving you > from the effort of making your own printf stuff. >
IMHO, I will have an error from the linker, because of multiple definition of malloc and free.
Il 30/08/2019 09:57, David Brown ha scritto:
> [...]while saving you > from the effort of making your own printf stuff.
I found this: https://github.com/mpaland/printf
pozz <pozzugno@gmail.com> writes:
> How to remove completely the heap with sprintf functions? Maybe by > implementing a custom minimal sprintf function?
Use snprintf instead of sprintf on general principles, but both might allocate some temp space for conversions. All I can say is see if you can check the source code for the implementation. You could put in a fake malloc that crashes immediately, and see if sprintf actually calls it with the data that your program passes it. If it doesn't, that's not a guarantee, but it is a point of information.
On 30/08/2019 10:23, pozz wrote:
> Il 30/08/2019 09:57, David Brown ha scritto: >> On 30/08/2019 09:29, pozz wrote: >>> I'd like to avoid using dynamic memory (heap) at all. I'm using gcc for >>> ARM and newlib (really I'm using NXP MCUXpresso IDE, but I don't think >>> this is important). >>> >>> When the code is minimal, I noticed heap related functions (malloc, free >>> and so on) aren't included in the output binary file. >>> However, as soon as I use sprintf-like functions, heap is used. >>> >>> I looked at the newlib source code and indeed I noticed __ssputs_r(), a >>> common shared function between sprintf-like functions, that uses malloc. >>> I think it uses malloc for asprintf-like functions that I'm not using. >>> >>> How to remove completely the heap with sprintf functions? Maybe by >>> implementing a custom minimal sprintf function? >> >> Check how __ssputs_r is using the heap.&#2013266080; Maybe you can get away with >> just adding these to your code somewhere: >> >> static char heap[100]; >> void * malloc(size_t size) { >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;(void) size; >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;return heap; >> } >> >> void free(void *ptr) { >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;(void) ptr; >> } >> >> If the __ssputs_r function only ever calls malloc once while it works, >> and you don't use it from different threads or contexts (the "_r" in >> newlib is for reentrant code), that might do the job, while saving you >> from the effort of making your own printf stuff. >> > > IMHO, I will have an error from the linker, because of multiple > definition of malloc and free.
No, you won't. The linker first loads the object files you have specified for linking. It resolves all the symbols there when it can, and collects the unresolved ones. Then it pulls in the libraries, and uses them to resolved those symbols. That can lead to new unresolved symbols, which are again pulled from the libraries (and so on). Symbols defined in your object code - or in a library that is loaded before newlib - will get priority. It is not uncommon to have your own stub definitions of library functions like "exit" or "atexit" in order to avoid extra code being pulled in from libraries when you know your code will never call them.
Il 30/08/2019 10:48, David Brown ha scritto:
> On 30/08/2019 10:23, pozz wrote: >> Il 30/08/2019 09:57, David Brown ha scritto: >>> On 30/08/2019 09:29, pozz wrote: >>>> I'd like to avoid using dynamic memory (heap) at all. I'm using gcc for >>>> ARM and newlib (really I'm using NXP MCUXpresso IDE, but I don't think >>>> this is important). >>>> >>>> When the code is minimal, I noticed heap related functions (malloc, free >>>> and so on) aren't included in the output binary file. >>>> However, as soon as I use sprintf-like functions, heap is used. >>>> >>>> I looked at the newlib source code and indeed I noticed __ssputs_r(), a >>>> common shared function between sprintf-like functions, that uses malloc. >>>> I think it uses malloc for asprintf-like functions that I'm not using. >>>> >>>> How to remove completely the heap with sprintf functions? Maybe by >>>> implementing a custom minimal sprintf function? >>> >>> Check how __ssputs_r is using the heap.&#2013266080; Maybe you can get away with >>> just adding these to your code somewhere: >>> >>> static char heap[100]; >>> void * malloc(size_t size) { >>> &#2013266080;&#2013266080;&#2013266080;&#2013266080;(void) size; >>> &#2013266080;&#2013266080;&#2013266080;&#2013266080;return heap; >>> } >>> >>> void free(void *ptr) { >>> &#2013266080;&#2013266080;&#2013266080;&#2013266080;(void) ptr; >>> } >>> >>> If the __ssputs_r function only ever calls malloc once while it works, >>> and you don't use it from different threads or contexts (the "_r" in >>> newlib is for reentrant code), that might do the job, while saving you >>> from the effort of making your own printf stuff. >>> >> >> IMHO, I will have an error from the linker, because of multiple >> definition of malloc and free. > > No, you won't. > > The linker first loads the object files you have specified for linking. > It resolves all the symbols there when it can, and collects the > unresolved ones. Then it pulls in the libraries, and uses them to > resolved those symbols. That can lead to new unresolved symbols, which > are again pulled from the libraries (and so on). > > Symbols defined in your object code - or in a library that is loaded > before newlib - will get priority. > > It is not uncommon to have your own stub definitions of library > functions like "exit" or "atexit" in order to avoid extra code being > pulled in from libraries when you know your code will never call them.
I didn't know this, yes it works. However I tried to replace sprintf() with a custom sprintf(), but in this case the linker complains with: multiple definition of `sprintf' It sees two definitions in my printf.c and in libc_nano.a. Could you argue why this happens with sprintf() and not with malloc()?
Pozz, you may want to have a look here:
http://www.nadler.com/embedded/newlibAndFreeRTOS.html
Hope that helps!
Best Regards, Dave
Il 30/08/2019 14:21, pozz ha scritto:
> Il 30/08/2019 10:48, David Brown ha scritto:
>[...]
> However I tried to replace sprintf() with a custom sprintf(), but in > this case the linker complains with: > > &#2013266080; multiple definition of `sprintf' > > It sees two definitions in my printf.c and in libc_nano.a. Could you > argue why this happens with sprintf() and not with malloc()?
After seen that -O1 linker option solves this problem, I found the option --allow-multiple-definition that works even with -O0, that I use in debug build.
On 30/08/2019 15:39, pozz wrote:
> Il 30/08/2019 14:21, pozz ha scritto: >> Il 30/08/2019 10:48, David Brown ha scritto: > >[...] >> However I tried to replace sprintf() with a custom sprintf(), but in >> this case the linker complains with: >> >> &#2013266080;&#2013266080; multiple definition of `sprintf' >> >> It sees two definitions in my printf.c and in libc_nano.a. Could you >> argue why this happens with sprintf() and not with malloc()?
Are you sure? Have you checked the map file (and given the linker settings for showing detailed information in the map file) ?
> > After seen that -O1 linker option solves this problem, I found the option > > &#2013266080; --allow-multiple-definition > > that works even with -O0, that I use in debug build. >
It seems risky to allow multiple definitions. It's not an option I have used - I'd need to look more deeply into documentation before I can be more helpful. I never recommend using -O0, for any purpose. Code at -O1 (on gcc at least) is much easier for debugging, with far clearer assembly. More importantly, it enables a lot more analysis on the compiler and therefore gives far more accurate static warnings. I usually compile at approximately -O2 (by "approximately", I mean I often have some additional specific flags added). It can make single-step debugging difficult because it re-arranges code a lot, so occasionally I will go down to -O1 for a file if I need to use a lot of single-step or assembly level debugging on it. I am strongly against the whole concept of "debug build" and "release build" if it encourages you to use different optimisation levels and settings. Enabling or disabling verbose outputs for different bits of the code is okay.