Using std::vector ?

Status
Not open for further replies.

MDashdotdashn

New member
Hi All,

I'm trying to use std::vector in a project I'm doing. This part of code is non-critical and the usage of std::vector makes the code a lot more readable than any 'custom' made solution.

However, as soon as I try to use std::vector<T>::insert() , the linker complains

SynthController.cpp.o: In function `std::vector<unsigned char, std::allocator<unsigned char> >::_M_check_len(unsigned int, char const*) const':
/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/include/c++/4.7.2/bits/stl_vector.h:1306: undefined reference to `std::__throw_length_error(char const*)'
collect2: error: ld returned 1 exit status

From what I can gather, it seems that exception handling isn't linked with the rest, which makes sense because everything is compiled with exception disabled.

What puzzles me is that from what I can read, there is a way to use STL with exception disabled and this should be automatically done when specifying no exception support from gcc.

Any idea on how to solve this or should I write an equivalent container myself ?

Thanks
Marc
 
Anyone know about this? I can't get my vectors to work. But I am new to vectors.
It compiles, but the board faults on download.

v.push_back(i); isn't working either...doesn't compile

Code:
#include <vector>

std::vector<int> v;

void setup (){
  Serial.begin(115200);
  
  for (int i=0; i<5; i++){
    v[i]=i;
   // v.push_back(i);
  }
}

void loop () {
  for (int i=0; i<5; i++){
    Serial.println(v[i]);}
  delay(1000);
}
 
Last edited:
I've always avoided the use of STL on embedded code. I don't know if there is a good implementation or subset that is useful, but I'm kind of wary of the hidden memory management disaster that it may create. Moving stuff around STL containers implies a lot of internal new/delete actions going on.

On embedded I usually code C++ as more of a subset, like "C plus classes". But I would also be interested to know if there is a usable set of STL (or for that matter, a good writeup on using new/delete/malloc/etc on ARM).
 
I'm making pretty extensive use of stl and C++ in a project. I mostly create containers at startup at startup, use reserve() to preallocate, and try to avoid extending them beyond that allocated size. I've wrapped the STL containers so that in the future I could replace them with fixed sized inline variants.

While I would like to use exceptions, I've avoided them primarily because the sup_c++ library creates a fixed and, as far as I can tell, unchangeable 2k buffer for use when handling out of memory exceptions.

I'm using the ARM gcc toolchain. It has a set of libxxx_s.a libraries that are significantly smaller than the standard ones. And it's a modern compiler with C++11 features like lambda. Also, I redefine the function __cxa_demangle, which reduces code size dramatically.

I've also played with USTL but surprisingly, I've found the gcc stl to be significantly smaller. The maintainer of ustl seems unwilling to accomodate embedded systems.

I use this command to examine the size of symbols (and figure out flash usage):

Code:
arm-none-eabi-nm --size-sort -S -l -C -A main.elf

I have some teensy 3 experiments with c++/vector posted here. And the main project is here (but uses an nrf 51822 chip, not the teensy).

Hope this helps,

-c
 
I re-read my comments above, and while I think they are all true, they're not very concretely helpful. I'd like to help, though.

To that end, I've written a simple test program. The main guts of it are:

Code:
string hello = "hello ";
uint32_t size = 10;
vector<string> vec;
vec.reserve(size);

int i = 0;

while(1) {
  if (i++ % size == 0) {

    for(auto it = vec.begin(); it != vec.end(); ++it) {
      Debug.println(it->c_str());
    }
    Debug.println("-------------");
    Debug.flush();

    vec.clear();
    flash_led();
  }

  vec.push_back(hello);

  delay(100);

}

The complete program and Makefile are checked into bitbucket.

I used the following compiler options. I'm assuming here that you know your way around a Makefile. I don't use the Arduino IDE so I'm not sure what would be involved in trying to get these Makefile options working in that GUI. Perhaps someone else can help with that.

  • OPTIONS = -DF_CPU=48000000 -DUSB_SERIAL -D__MK20DX128__ -DLAYOUT_US_ENGLISH -DSYSCALLS -DARDUINO=100 -DSTLVECTOR
  • CPPFLAGS = -Wall -g -Os -Wall -mcpu=cortex-m4 -mthumb -MMD -fshort-wchar $(OPTIONS) -I. -ffunction-sections -fdata-sections
  • CXXFLAGS = -std=c++0x -felide-constructors -fno-exceptions -fno-rtti
  • Using gcc not g++ to compile C++ files: CXX = $(abspath $(COMPILERPATH))/arm-none-eabi-gcc
  • Also using gcc as the linker (is this the default in Pauls' makefile?)
  • LIBS = -lsupc++ -lm -lc -lstdc++
  • LDFLAGS=-Os -mcpu=cortex-m4 -mthumb -Tmk20dx128.ld -L. -Wl,--gc-sections

The resulting compile line is :

Code:
arm-none-eabi-gcc -std=c++0x -felide-constructors -fno-exceptions -fno-rtti -Wall  -g -Os -Wall -mcpu=cortex-m4 -mthumb -MMD -fshort-wchar -DF_CPU=48000000  -DUSB_SERIAL -D__MK20DX128__ -DLAYOUT_US_ENGLISH -DSYSCALLS -DARDUINO=100 -DSTLVECTOR -I. -ffunction-sections -fdata-sections  -c -o main_vector.o main_vector.cpp

and link line:

Code:
arm-none-eabi-gcc -Os -mcpu=cortex-m4 -mthumb -Tmk20dx128.ld -L. -Wl,--gc-sections -o main_vector.elf main_vector.o analog.o Print.o mk20dx128.o nonstd.o pins_teensy.o syscalls.o usb_desc.o usb_dev.o yield.o usb_debug_hid.o usb_inst.o usb_mem.o usb_serial.o  -lsupc++ -lm -lc -lstdc++

I've tried a few different compilers:


gcc version 4.4.1 (Arduino 1.0.1-beta2??)
text data bss dec hex filename
66416 1548 6748 74712 123d8 main_vector.elf​

gcc version 4.7.2 (PJRC Build of GNU Toolchain from CodeSourcery):
text data bss dec hex filename
79636 1668 6772 88076 1580c main_vector.elf​

gcc version 4.7.3 20121207 (release) [ARM/embedded-4_7-branch revision 194305] (GNU Tools for ARM Embedded Processors):
text data bss dec hex filename
96036 2428 6772 105236 19b14 main_vector.elf​

These all result in working programs but they are unusably large. 60k+ wtf? Most of this comes from standard library functions:

Code:
arm-none-eabi-nm --size-sort -S -l -C -A main_vector.elf 
...
main_vector.elf:1ffff780 00000800 b emergency_buffer
main_vector.elf:1fffe200 00000870 B usb_buffer_memory	/Users/cmason/code/teensy3/foo/teensy/usb_mem.c:10
main_vector.elf:0000daec 00000ab0 T _svfiprintf_r
main_vector.elf:0000b8d8 00000f2e T _dtoa_r
main_vector.elf:0000a060 00001538 T _svfprintf_r
main_vector.elf:00005464 000022be t d_print_comp

However, this last toolchain from ARM has special (smaller) versions of the standard libraries whose names end in _s.

  • Using LIBS=-lsupc++_s -lm -lc_s -lstdc++_s

gcc version 4.7.3 20121207 with _s libraries
text data bss dec hex filename
11076 268 4460 15804 3dbc main_vector.elf​

This is much better (and still works fine). The BSS section still seems pretty huge though, but most of this seems to be coming from teensy 3 buffers:

Code:
... (only showing bss here)
main_vector.elf:1fffecca 000000fa b tx_buffer	/Users/cmason/code/teensy3/foo/teensy/serial1.c:10
main_vector.elf:1ffff234 00000103 B i2c_t3::rxBuffer
main_vector.elf:1ffff12c 00000103 B i2c_t3::txBuffer
main_vector.elf:1fffe000 00000200 b table	/Users/cmason/code/teensy3/foo/teensy/usb_dev.c:14
main_vector.elf:1fffe200 00000870 B usb_buffer_memory	/Users/cmason/code/teensy3/foo/teensy/usb_mem.c:10

I also have some pointers for if you do want to try exceptions, just ask. I doubt anyone cares though.

As I said before, I've also experimented with ustl but found it significantly larger and a pain to deal with on embedded. I don't think it's the code generation or headers for C++ that are the problems; it's the libraries that suck. The default implementations of the standard libraries just make all sorts of assumptions that only apply on desktop systems.

So the bottom line is, use the ARM toolchain and the _s libraries.

How else can I help? I hate hearing people say "don't use C++ on embedded." Modern C++ is awesome. :)

-c
 
Last edited:
Thanks for posting all this useful information cmason. You obviously have a good understanding of these topics and have spent time studying it.

ARM is a new thing to me, and I think my reservations, and perhaps others, comes from just the opaque nature of the libraries, tools, and resulting output (not to mention the dictionary of command line options for gcc). I certainly would not have expected such wildly different outputs from the handful of gcc versions you tried (and I've never even heard of the _s libraries). I'll be sure to study all this.
 
The BSS section still seems pretty huge though, but most of this seems to be coming from teensy 3 buffers:

Reducing the RAM usage is on my to-do list for 1.15 or maybe 1.16, and honestly I could really use some help on one part.

The easiest is "table" from usb_dev.c. It's not supposed to be 512 bytes. I had meant to make it something like this:

Code:
static bdt_t table[(NUM_ENDPOINTS+1)*4];

The biggest memory hog, usb_buffer_memory, is a necessary evil. This can be adjusted by editing usb_desc.h, but the USB stack really does need buffer memory for incoming and outgoing packets. The USB peripheral works by doing DMA transfers to/from those buffers.

The part where I could really use some help, if anyone has any insight, would be things like rx_buffer and tx_buffer from serial1.c, serial2.c and serial3.c. These appear to be getting linked into all programs, even if Serial1, Serial2 and Serial3 are never used. I'm pretty sure the reason is because the vector table has the address of the interrupt routine, which forces the linker to include the ISR function, which then forces the linker to allocate the memory the ISR function uses.

On AVR, the interrupt vector table somehow created with symbols like __vector2 that seem to work by magic. If the linker brings in code from any functions in the same file (compile unit), then the vector table gets the address of the ISR functions. But if none of the other non-ISR code is linked from that file, the linker somehow puts the address of a dummy do-nothing ISR into the vector table. That avoids brining all that unused code into the executable, and also avoids allocating all statically allocated RAM those functions would have used.

If anyone knows how the AVR platform achieves this linker magic, please tell me?! Eventually I'll dig into it... because this is really very desperately needed. Even just a hint of where to look or what to search for might really help?
 
Was there ever a working answer that doesn't involve in hacking up makefiles to use STL? I was able to use the STL implementation with 1.0.6 and the Arduino from uclibc++ and everything worked really well. However, I get the errors above now with the Teensy 3.1 compiler. Same error with the uclibc++ and with the built in STL support.

Or, is there another implementation of a vector/list that I don't have to write myself that I can pull? I've found some options, but I really like STL because it just works, is relatively easy to port, and generally isn't a source of bugs. Might not be the cheapest implementation, but again, it just works. Function over form in this case.
 
I've been using STL a little in a Teensy 3.1 Library project.

Initially I was only using std::vector, but then I decided I wanted more.

In order to use std::vector, I found that I had to include the header

Code:
#include<vector>

and also define a couple of loose ends

Code:
namespace std {
  void __throw_bad_alloc()
  {
    Serial.println("Unable to allocate memory");
  }

  void __throw_length_error( char const*e )
  {
    Serial.print("Length Error :");
    Serial.println(e);
  }
}

Next I decided to try to use std:string. This wasn't so easy, since I got a lot of undefined std:string functions from the linker. I'm guessing vector is header only code, but the string code uses the library.

Arduino String is still intact because it is captial 's' String, whereas the c++ library version is little 's' string.

Header file

Code:
#include<string>

The answer was to add -lstdc++ to the library line in the boards.txt file:

Code:
teensy31.build.flags.libs=-larm_cortexM4l_math -lm -lstdc++

This resulted in multiply defining the two functions above (presumably because they're defined in the library!) and leaving two more undefined. The two functions above went, and the two below got added:

Code:
extern "C"{
  int _getpid(){ return -1;}
  int _kill(int pid, int sig){ return -1; }
}

Now my code compiles and appears to work. It should be noted that the program size more than doubled from approximately 18% to 41%....
 
Last edited:
Awesome, I can't wait to try it. I wondered if it was that easy, but haven't had a chance to work on it. I appreciate the help. I'll try them both, but I have tons of space for program here, but figuring out how to implement vector for all the types I'm using it with would take a ton of time.
 
I just did a quick (informal) check about code size. As you would hope, adding

<code>-lstdc++</code>

to the teensy31.build.flags.libs property adds zero bytes to the compiled program size of programs not using any C++ features.

Damn some of it is big, though, when you do decide to use it. I had to back off from using stringstream. It made my code size go from 40% to 89%! However, vector, string and algorithm are in with minor impact.

Paul, I wonder if this library could be included by default? With that and the two functions(_getpid, _kill) stubbed out, it would enable all the great STL etc. stuff we expect from a modern C++ environment with, I think, no impact on code not using the features.
 
That did it for me. Yeah, code size more than doubled, but it's what I wanted. I have tons of space here and I won't use it all. Thanks so much for the help.
 
std::vector and streaming library

I was happy to see this ran OK on T3.1:

Code:
#include <stack>
#include <vector>
#include <Streaming.h>
// First install Arduino streaming library http://arduiniana.org/Streaming/Streaming5.zip
 
struct TestStack {
  static void RunTest() {
 
    std::stack<int,std::vector<int> > stk;
    int i;
 
    for(i=0;i<20;i++)
      stk.push(i);
 
    while(!stk.empty()) {
      Serial << stk.top() << ' ';
      stk.pop();
    }
    Serial << endl;
  }
}; 

void setup() {
  delay(2000);
  Serial << "Starting serial test..." << endl;
  TestStack::RunTest();
}

void loop() { }
 
Status
Not open for further replies.
Back
Top