std :: array模板实例会占用更多代码内存吗?

use*_*735 1 c++ templates code-size c++11 stdarray

我有一个没有MMU的微控制器,但我们使用的是C和C++.

我们避免使用所有动态内存(即没有new SomeClass()malloc())和大多数标准库.

半问题0:

据我所知std::array,不使用任何动态内存,所以它的用法应该没问题(它只在堆栈上).查看std::array源代码,它看起来很好,因为它创建了一个c风格的数组,然后围绕该数组包装功能.

我们使用的芯片有1MB的闪存用于存储代码.

问题1:

我担心使用模板std::array会导致二进制文件变大,这可能会导致二进制文件超过1MB的代码内存限制.

我想如果你创建一个a的实例std::array< int, 5 >,那么所有对函数的调用std::array都将占用一定数量的代码内存,让我们说X字节的内存.

如果你创建了另一个实例std::array< SomeObject, 5 >,然后调用函数,那么std::array现在每个函数都会在二进制文件中重复,从而占用更多的代码内存吗?X字节的内存+ Y字节的内存.

如果是这样,您认为在有限的代码内存容量下生成的代码量是一个问题吗?

问题2:

在上面的示例中,如果您创建了第二个std::array< int, 10 >实例,对函数的调用是否也会复制生成的代码中的函数调用?尽管这两种情况都是同一类型的,int

Gui*_*cot 6

std::array 被认为是零成本抽象,这意味着编译器应该可以相当优化.

从任何零成本抽象开始,它可能会导致较小的编译时间,并且如果所需的优化确实不支持零成本,那么它可能会产生小的尺寸或运行时间.

但是,请注意编译器可以在结构的末尾添加填充.既然std::array是一个结构,你应该检查你的平台如何处理std::array,但我非常怀疑你的情况.

拿这个数组和std::array大小写:

#include <numeric>
#include <iterator>

template<std::size_t n>
int stuff(const int(&arr)[n]) {
    return std::accumulate(std::begin(arr), std::end(arr), 0);
}

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6};
    return stuff(arr);
}
Run Code Online (Sandbox Code Playgroud)
#include <numeric>
#include <iterator>
#include <array>

template<std::size_t n>
int stuff(const std::array<int, n>& arr) {
    return std::accumulate(std::begin(arr), std::end(arr), 0);
}

int main() {
    std::array arr = {1, 2, 3, 4, 5, 6};
    return stuff(arr);
}
Run Code Online (Sandbox Code Playgroud)

Clang非常支持这种情况.所有带std::array或原始数组的情况都是以同样的方式处理:

-O2/ -O3数组和std::arrayclang:

main: # @main
  mov eax, 21
  ret
Run Code Online (Sandbox Code Playgroud)

但是,对于bith std::array和原始数组的情况,GCC似乎在优化它时遇到了问题:

-O3使用GCC进行数组和std::array:

main:
  movdqa xmm0, XMMWORD PTR .LC0[rip]
  movaps XMMWORD PTR [rsp-40], xmm0
  mov edx, DWORD PTR [rsp-32]
  mov eax, DWORD PTR [rsp-28]
  lea eax, [rdx+14+rax]
  ret
.LC0:
  .long 1
  .long 2
  .long 3
  .long 4
Run Code Online (Sandbox Code Playgroud)

然后,它似乎-O2在原始数组的情况下更好地优化并且失败std::array:

-O2GCC std::array:

main:
  movabs rax, 8589934593
  lea rdx, [rsp-40]
  mov ecx, 1
  mov QWORD PTR [rsp-40], rax
  movabs rax, 17179869187
  mov QWORD PTR [rsp-32], rax
  movabs rax, 25769803781
  lea rsi, [rdx+24]
  mov QWORD PTR [rsp-24], rax
  xor eax, eax
  jmp .L3
.L5:
  mov ecx, DWORD PTR [rdx]
.L3:
  add rdx, 4
  add eax, ecx
  cmp rdx, rsi
  jne .L5
  rep ret 
Run Code Online (Sandbox Code Playgroud)

-O2 GCC原始数组:

main:
  mov eax, 21
  ret
Run Code Online (Sandbox Code Playgroud)

似乎GCC错误很容易优化-O3但成功与-O2最新版本修复.

这是一个包含所有O2和所有的编译器资源管理器O3


在陈述所有这些情况之后,您可以看到一个共同的模式:没有关于std::array二进制文件输出的信息.没有构造函数,没有operator[],甚至没有迭代器,也没有算法.一切都是内联的.编译器擅长内联简单的功能.std::array成员函数通常非常简单.

如果你创建std :: array <SomeObject,5>的另一个实例,然后调用那个std :: array的函数,那么现在每个函数都会在二进制文件中重复,从而占用更多的闪存吗?X字节的内存+ Y字节的内存.

好吧,您更改了数组包含的数据类型.如果你手动添加所有函数的重载来处理这个额外的情况,那么是的,所有这些新函数可能会占用一些空间.如果您的功能很小,那么它们很有可能被内联并占用更少的空间.正如您在上面的示例中所看到的,内联和常量折叠可能会大大减少二进制大小.

在上面的例子中,如果你创建了第二个std :: array实例,对函数的调用是否也会复制闪存中的函数调用?即使两个实例的类型相同,int?

再取决于它.如果您有许多以数组大小模板化的函数,则两个std::array和原始数组都可以"创建"不同的函数.但同样,如果它们被内联,那么就没有重复的担心了.

两者都是一个原始数组std::array,你可以将指针传递给数组的开头并传递大小.如果您发现这更适合您的情况,那么使用它,但仍然是原始数组,并且std::array可以做到这一点.对于原始数组,它会隐式衰减到指针,并且std::array必须使用它arr.data()来获取指针.