I have been looking at using the newer C++ language features such as iterators on embedded systems (16KB of SRAM and 64 KB of flash, Cortex M4) and hit a surprising roadblock. Why on earth are iterators so monstrously large? I was under the impression they are basically some pointer arithmetic or indexing. Is the STL pulling in some unexpected code?
These are using Kinetis Design Studio on windows with the gcc-arm-none-eabi-4_9 toolchain from here using the following flags.
arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Os -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -fsingle-precision-constant -flto -g3 -I"../Sources" -I"../Includes" -std=gnu++11 -fabi-version=0 -std=c++11 -MMD -MP -MF"Sources/System.d" -MT"Sources/System.o" -c -o "Sources/System.o" "../Sources/System.cpp"
ITM_SendChar just takes a single char and puts it in a register.
std::string input = "Oh hai there! :D\n";
#ifdef char_array
// .text 7352
// .data 376
// .bss 236
for(int i = 0; i < input.size(); i++)
ITM_SendChar(input[i]);
#endif
#ifdef char_at
// .text 7392
// .data 376
// .bss 236
for(int i = 0; i < input.size(); i++)
ITM_SendChar(input.at(i));
#endif
#ifdef char_itterator
// .text 39744
// .data 384
// .bss 252
for(char some_char : input)
ITM_SendChar(some_char);
#endif
#ifdef char_itterator_auto
// .text 39744
// .data 384
// .bss 252
for(auto some_char : input)
ITM_SendChar(some_char);
#endif
#ifdef char_itterator_auto_no_copy
// .text 39744
// .data 384
// .bss 252
for(auto& some_char : input)
ITM_SendChar(some_char);
#endif
The main difference between the [] operator and .at() is that .at() does bounds checking, and will throw an exception if the index is out of bounds.
It seems likely that the standard library implementation you're using is linking in extra code for something when using an iterator. The only way to find the cause is to examine the linker map file for both versions, and look closely at source code for the functions you're using, and maybe the generated assembly too.
In general, if you need your code to be very small you want to avoid using any of the standard library, because the functions in there can pull in lots of code and data with them. Even the code that parses the command line into the format that main() expects can be quite big.
For comparison, try this:
const char *input = "Oh hai there! :D\n";
while (*input)
ITM_SendChar(*input++);
One (or two) C++ standards ago it was legal for iterators to be implemented with pointers. (You can google "removed weasel wording" in the standard to find out more about this.) Newer standards require more from iterators, for example that if you have two corresponding iterators in two containers of the same type, then swapping those two containers also requires swapping those two iterators (see N4527 23.2.1 footnote 9 to read it for yourself, if you like). All of this means that indexing into a container with indexes instead of iterators can certainly be more efficient. It's just that that isn't supported for all standard container types... And that's also why using iterators increases code size.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With