0

This compiler business is not my forte...

I want to use the <string> template library in my embedded c++ code.

For example (sudo code):

#include <string>

int main() {
  std::string str = std::to_string(3.87628);
}

When I try to compile this code, I get the error:

error: 'to_string' is not a member of 'std'

My Makefile contains the following flags:

# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"

# C++ Flags
CPPFLAGS = $(CFLAGS)
CPPFLAGS += \
-fno-exceptions \
-fno-rtti 

C_STANDARD = -std=gnu11
CPP_STANDARD += -std=gnu++14

To my understanding, the inclusion of flag -std=gnu++14 should have me covered no?

scottc11
  • 716
  • 2
  • 7
  • 20
  • Maybe the compiler is too old? `--version`? – HolyBlackCat Jan 13 '22 at 21:07
  • 1
    Side note: You can't do that anyway. The array of unknown size needs a brace-enclosed list, not a `std::string`. – user4581301 Jan 13 '22 at 21:09
  • 1
    I wonder if this is a result of `-fno-exceptions`. `std::to_string`'s entire error handling is based on `throw`ing exceptions. – user4581301 Jan 13 '22 at 21:12
  • 1
    You not only need a compiler new enough to recognize `-std=gnu++14` but also a C++14 set of standard header files. Unfortunately your embedded toolchain vendor might have substituted the headers with a feature-reduced set. – Ben Voigt Jan 13 '22 at 21:12
  • --version gives me `arm-none-eabi-g++ (GNU Arm Embedded Toolchain 9-2020-q2-update) 9.3.1 20200408 (release)` – scottc11 Jan 13 '22 at 21:15
  • Not sure how authoritative this is, but [quoting developer.arm.com](https://developer.arm.com/documentation/dui0774/i/Compiler-Command-line-Options/-fexceptions---fno-exceptions): *Compiling with -fno-exceptions disables exceptions support and uses the variant of C++ libraries without exceptions.* – user4581301 Jan 13 '22 at 21:15
  • like, I am so confused as to what "version of stl" my compiler is using... – scottc11 Jan 13 '22 at 21:17
  • what is available to me, what isn't available to me ‍♂️‍♂️ – scottc11 Jan 13 '22 at 21:18
  • You ask a damn good question. Remove the `-fno-exceptions` from `CPPFLAGS` in the makefile and see if you get a different set of error messages. – user4581301 Jan 13 '22 at 21:19
  • removing `-fno-exceptions` flag still gives me `error: 'to_string' is not a member of 'std'` – scottc11 Jan 13 '22 at 21:24
  • what does the flag `-specs=nano.specs` do? – scottc11 Jan 13 '22 at 21:27
  • Dunno what that flag does off the top of my head. I'd have to look it up. Looks like you have a reduced set. Period. Can't find good documentation on what that reduced set entails, but I'd start from the assumption you've been chopped back to the C Standard Library. Hunt down the include\c++\9.3.1 folder and see what's in it. Your compiler can FIND `` or you'd get a different message, so we're looking for a macro that disables functionality. That can be a nightmare to trace. – user4581301 Jan 13 '22 at 21:33
  • 2
    Simply so we have a program that *should* compile, could you replace the function body with `std::to_string(3.87628);`? It's just possible that your compiler is giving you a really funky diagnostic. – Bathsheba Jan 13 '22 at 21:45
  • Yeah I'm following everyones suggestions and still get the same error. As @user4581301 said, its quite difficult to find the macro disabling the function, if at all. I looked into the folder `/opt/homebrew/Cellar/gcc-arm-none-eabi/20200630/arm-none-eabi/include/c++/9.3.1/bits/basic_string.h` and can't make any sense of the macros defined, and VSCode usually greys out anything that is being skipped by a undefined macro, but I have no idea how accurate that is. – scottc11 Jan 13 '22 at 22:07
  • For VS code to highlight stuff correctly, intelisense's parser needs to have the same options set as those used to build. My VS Code-fu is too weak to tell you which JSON file needs to be edited to match the makefile. – user4581301 Jan 13 '22 at 22:09
  • 1
    Let's have some more fun. Replace your function body with `std::string cplusplus03("Hello");`, then replace it with `std::string cplusplus11{"Hello"};`. If neither compile then `std::string` is bust. If the first one compiles and the second one doesn't then you don't have a C++11 or later compiler. If both compile then so should `std::to_string` **without** that dodgy assignment. If the first one doesn't compile and the second one does then drink a bottle of whisky. – Bathsheba Jan 13 '22 at 22:13
  • STL is not relevant to this question. `` is not part of STL. STL itself is a subset of the Standard C++ Library. – Clifford Jan 13 '22 at 22:44
  • 1
    @user4581301 compiler "predfined macros" will also need to be set and these may change with switches such as `-std=gnu++14`. The predefined macros can be dumped as described at https://stackoverflow.com/questions/2224334/gcc-dump-preprocessor-defines – Clifford Jan 13 '22 at 22:50
  • Please don't use C stringification macros, as it would solve the problem in 10 seconds. `#define S(x) #x`, `#define STR(x) S(x)` ... `char str[] = STR(3.1415);`. It's important that we spend at least a week figuring out what parts of C++ libs are supported, where in our MCU silent heap allocation go, and benchmarking the extra run-time overhead and stack use created by `std::string`. Then make a convincing argument against industry standards why our product must use heap allocation, when none else do. We must not use C or we'll be focusing too much on the actual project and application. – Lundin Jan 14 '22 at 09:49
  • If the intent of the code is to convert a _double_ to a string, the code in the question is not a good illustration - as a literal _double_ the simple answer is as Lundin has suggested to use a _literal string_ instead. But perhaps you want a generalised solution that would work for _double variables_? – Clifford Jan 16 '22 at 17:43

2 Answers2

1

std::to_string is part of the library, not the language. So -std=gnu++14 will have no effect if it is not implemented in the library. Check the header file to see if

  • it is declared, and
  • if it is dependent on any macros being defined.

The library is separate from the compiler, and your toolchain may be using an older C++ library or a cut-down library aimed at embedded systems. From where did your source the toolchain and what C++ library is it using? The copyright message in the header may provide a clue - including version information.

The std::string library is often inappropriate in embedded systems because amongst other issues it relies on non-deterministic dynamic memory allocation.

Since your code suggests you are actually using C strings rather than std::string you might consider:

#include <cstdio>

int main() 
{
    char str[32] ;
    std::snprintf( str, sizeof(str), "%f", 3.87628 ) ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Since it's a float constant, then this code is almost as bad as using `std::string`. `snprintf` might not use the heap but it comes with tons of overhead. The correct solution is `#define S(x) #x`, `#define STR(x) S(x)` ... `char str[] = STR(3.87628);`. Or simply `char str[] = "3.87628";`. – Lundin Jan 14 '22 at 09:53
  • @Clifford is `snprintf()` part of `std`? My compiler says it is not. I removed std:: and your code compiled, but only a blank string gets printed to the console. No number / float. – scottc11 Jan 14 '22 at 14:11
  • @Lundin could you perhaps post an answer? – scottc11 Jan 14 '22 at 14:12
  • @lundin Yes, I am assuming the constant in the original question was purely illustrative and not real code. In real code that overhead may already have been paid by usage elsewhere. It is probably stack hungry. Without context it is hard to say if this is a reasonable method. I offer it simply as a method using standard library functions. SO is full of more optimal coded solutions. – Clifford Jan 15 '22 at 10:38
  • @scottc11 Yes library implementations vary, and standards change. The code writes a string to the array `str`. Why would you expect output to the console? Your code would not do that either. – Clifford Jan 15 '22 at 10:45
  • @Clifford that was just sudo code, I have a UART peripheral set up with a driver an everything – scottc11 Jan 15 '22 at 15:41
  • @scottc11 I am not sure what point you are making or which comment you are addressing. _pseudocode_ BTW - but I am also not sure what code you are referring to either. My point was that _my_ code does not output to the console. If you have written does that is supposed to do that and it does not work, then that is an issue _your_ code not mine and is a different question. – Clifford Jan 16 '22 at 17:53
-1

The presence of C++ in embedded systems is problematic. Lets run your code on an g++ ARM32 10.2.1 gcc none-eabi compiler with maximum optimizations -O3 -std=c++17 enabled:

.LC0:
        .ascii  "%f\000"
main:
        str     lr, [sp, #-4]!
        adr     r1, .L15
        ldmia   r1, {r0-r1}
        sub     sp, sp, #36
        stm     sp, {r0-r1}
        ldr     r3, .L15+8
        add     r0, sp, #8
        mov     r2, #328
        ldr     r1, .L15+12
        bl      std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned int, char const*, std::__va_list), unsigned int, char const*, ...)
        ldr     r0, [sp, #8]
        add     r3, sp, #16
        cmp     r0, r3
        ldrne   r1, [sp, #16]
        addne   r1, r1, #1
        blne    _ZdlPvj
        mov     r0, #0
        add     sp, sp, #36
        ldr     lr, [sp], #4
        bx      lr
.L15:
        .word   380705901
        .word   1074725535
        .word   .LC0
        .word   vsnprintf

Now wth is all this bloat!? Did I forget the -O3 flag, huh, no I didn't... The program you posted has no side effects. Or so we thought - we've been cplusplus:ed. I would have expected a correctly behaving compiler to generate no code under -O3.

So the question is clearly not how to use std::string in the project and the answer isn't "In C++2x you didn't use constexpr this is not how you write modern C++ blahblah". The question is rather how to salvage the project once some PC programmer did drag this completely unacceptable code into your microcontroller just because someone decided to allow C++. The answer to that follows:

  • Step 1: switch to C. Same compiler ARM32 10.2.1 gcc none-eabi.

  • Step 2: don't waste time on bloat classes when there is no need for them. A simple thing such as converting a float constant to a string can and should be done in the pre-processor. Any intermediate-level C or C++ programmer ought to know about stringification macros:

    #define S(x) #x
    #define STR(x) S(x)
    
  • Step 3: test. A simple test program with -ffreestanding and some side-effect to ensure that the string doesn't get optimized out, for example:

      #include <stdio.h>
      #define S(x) #x
      #define STR(x) S(x)
    
      void main (void)
      {
        char str[] = STR(3.87628);
    
        puts(str); // just to introduce a side effect
      }
    

The relevant parts of the resulting machine code is now something like this:

.LC0:
        .ascii  "3.87628\000"
main:
        str     lr, [sp, #-4]!
        sub     sp, sp, #12
        mov     r3, sp
        ldr     r2, .L4
        ldm     r2, {r0, r1}
        stm     r3, {r0, r1}
.L4:
        .word   .LC0

This code is fine. We can get on with writing our application.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 2
    Your argument against programatically converting a literal `double` is valid if we assume that is what the OP wanted to do (although he was actually simply asking why `std::to_string` was not defined, so likely the code was only intended to reproduce that error). You have taken the opportunity to rail against C++ however with a rather weak argument. The technique you propose in C is also valid in C++ and compiles to identical code (compare https://godbolt.org/z/s5e91bqfY with https://godbolt.org/z/M6rrdhjsE) so it is not a clear argument against C++ . Too much opinion I think. – Clifford Jan 16 '22 at 18:17
  • @Clifford You can in _theory_ write efficient code in C++. The problem is that as soon as you allow C++, people start dragging in crap like std::string and from there it will be hard to restore the project to something sensible. – Lundin Jan 17 '22 at 07:01
  • 2
    That is a fair point, but does not make your argument here valid. It does not really matter as it is also not really an answer to the question in any event - more an extended comment on the wisdom of the code presented. – Clifford Jan 18 '22 at 13:49
  • 1
    Your "answer" is just a rant that doesn't answer OP's question. You're saying reasons to not want the STL in an embedded project, nobody asked for that. – Barnack Jul 20 '22 at 12:37
  • @Barnack This _is_ the answer from a professional point of view, as you can see from the benchmarking. And then it didn't even mention heap allocation, which is just as unacceptable. If someone asks a question "how do I best unscrew a screw using my teeth" then a professional has a duty to tell them "don't do that, it's a terrible idea, use the correct tool for the task, a screwdriver". But then of course some other DIY will post a comment half a year later "but the OP asked how to remove it using their _teeth_, this doesn't answer the question". – Lundin Jul 21 '22 at 13:11