与 at() 或索引相比,为什么使用 C++ 迭代器会显着增加代码大小?
Why does using C++ iterators increase code size drastically compared to at() or indexing?
我一直在考虑在嵌入式系统(16KB SRAM 和 64KB 闪存,Cortex M4)上使用更新的 C++ 语言功能,例如迭代器,但遇到了令人惊讶的障碍。为什么迭代器如此庞大?我的印象是它们基本上是一些指针运算或索引。 STL 是否引入了一些意外代码?
这些是在 windows 上使用 Kinetis Design Studio 和来自 here 的 gcc-arm-none-eabi-4_9 工具链,使用以下标志。
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 只需要一个字符并将其放入寄存器。
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
[]运算符和.at()的主要区别在于.at()会进行边界检查,如果索引越界就会抛出异常。
您使用的标准库实现似乎很可能在使用迭代器时链接了一些额外的代码。找到原因的唯一方法是检查两个版本的链接器映射文件,并仔细查看您正在使用的函数的源代码,也许还有生成的程序集。
一般来说,如果您需要非常小的代码,您希望避免使用任何标准库,因为其中的函数可以引入大量代码和数据。即使将命令行解析为 main() 期望的格式的代码也可能相当大。
为了比较,试试这个:
const char *input = "Oh hai there! :D\n";
while (*input)
ITM_SendChar(*input++);
一个(或两个)C++ 标准以前,用指针实现迭代器是合法的。 (您可以在标准中 google "removed weasel wording" 找到更多相关信息。)较新的标准对迭代器有更多要求,例如,如果您在两个相同类型的容器中有两个相应的迭代器,则交换这两个容器还需要交换这两个迭代器(如果愿意,请参阅 N4527 23.2.1 脚注 9 自己阅读)。所有这些都意味着使用索引而不是迭代器对容器进行索引肯定会更有效率。只是并非所有标准容器类型都支持它...这也是为什么使用迭代器会增加代码大小的原因。
我一直在考虑在嵌入式系统(16KB SRAM 和 64KB 闪存,Cortex M4)上使用更新的 C++ 语言功能,例如迭代器,但遇到了令人惊讶的障碍。为什么迭代器如此庞大?我的印象是它们基本上是一些指针运算或索引。 STL 是否引入了一些意外代码?
这些是在 windows 上使用 Kinetis Design Studio 和来自 here 的 gcc-arm-none-eabi-4_9 工具链,使用以下标志。
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 只需要一个字符并将其放入寄存器。
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
[]运算符和.at()的主要区别在于.at()会进行边界检查,如果索引越界就会抛出异常。
您使用的标准库实现似乎很可能在使用迭代器时链接了一些额外的代码。找到原因的唯一方法是检查两个版本的链接器映射文件,并仔细查看您正在使用的函数的源代码,也许还有生成的程序集。
一般来说,如果您需要非常小的代码,您希望避免使用任何标准库,因为其中的函数可以引入大量代码和数据。即使将命令行解析为 main() 期望的格式的代码也可能相当大。
为了比较,试试这个:
const char *input = "Oh hai there! :D\n";
while (*input)
ITM_SendChar(*input++);
一个(或两个)C++ 标准以前,用指针实现迭代器是合法的。 (您可以在标准中 google "removed weasel wording" 找到更多相关信息。)较新的标准对迭代器有更多要求,例如,如果您在两个相同类型的容器中有两个相应的迭代器,则交换这两个容器还需要交换这两个迭代器(如果愿意,请参阅 N4527 23.2.1 脚注 9 自己阅读)。所有这些都意味着使用索引而不是迭代器对容器进行索引肯定会更有效率。只是并非所有标准容器类型都支持它...这也是为什么使用迭代器会增加代码大小的原因。