1

我基本上是 C++ 的新手,除了 10 多年前尝试学习这门语言并放弃之外,因为我真的没有一个项目来激励我......无论如何,我只是说我很漂亮对 C++ 非常熟悉,让你们/女孩知道我目前的知识水平。也就是说,我相当精通 Python 和 PHP。而且由于这两种语言都是松散类型的,所以我不太熟悉 C++ 中的类型转换对可执行文件大小的影响(如果有的话)。

我正在编写一个 Arduino 程序,以从几个超声波距离传感器中获取一些数据,并将这些数据应用于伺服控制算法。没问题,但我现在正在尝试优化我的代码,因为我正在接近 Arduino Micro 的 28,672 字节限制。我的第一个想法是尽可能将我的数据类型更改为短整数和字符之类的东西,期望它要么没有效果,要么稍微减少我的可执行文件大小。我发现,在这些更改之后,可执行文件的大小实际上增加了几百字节。

比我有更多 C++ 知识的人能否帮助我理解其原因,以及为什么我应该或不应该为我的变量选择尽可能小的数据类型?显然结果决定了我应该在这里做什么,但我真的很想了解事情背后的“原因”,经过一些谷歌搜索,我仍然不确定。

另外,如果不是太多要求;有没有人有一些提示,或者链接到一些关于优化有限内存微控制器(如 Arduino)的 C++ 代码的信息?

4

4 回答 4

2

首先,看看如何优化您的 Arduino 内存使用优化 Arduino 内存使用。另外,看看节省 RAM 空间

于 2013-08-12T17:17:30.337 回答
2

你问了很多事情,但这可以用一个例子来回答:

我发现,在这些更改之后,可执行文件的大小实际上增加了几百字节。

...帮助我了解原因...

通常,您无法预测较小的数据类型是更好还是更差,下面的一小段代码将证明这一点。

要查看发生了什么,您必须查看编译器生成的汇编代码。AVR 工具链有一个组件可以生成这样的列表,通常是一个 .LSS 文件。我不认为 Arduino 支持这一点。下面的程序集清单是通过 Eclipse 生成的,它默认驱动扩展清单。

这是 LED 闪烁程序的一小部分,可用于证明您的困惑。它有一个在循环中设置给 LED 的亮度值:

boolean fadein = true;
int bright = 0;   // we will change this data type int <-> int8_t

void loop() {

  // adjust brightness based on current direction
  if(fadein) {
    bright += 1;
  }
  else {
    bright -= 1;
  }

  // apply current light level
  analogWrite(13,bright);

为了演示,bright变量在 1 字节和 2 字节 int 之间变化,我们比较汇编列表:


比较增量线

以下是具有两种数据类型的增量行的清单:

// int bright - increment line - must load and store 2 bytes
// 18 bytes of code

    bright += 1;
 18a:   80 91 02 01     lds r24, 0x0102
 18e:   90 91 03 01     lds r25, 0x0103
 192:   01 96           adiw    r24, 0x01   ; 1
 194:   90 93 03 01     sts 0x0103, r25
 198:   80 93 02 01     sts 0x0102, r24

第一列是代码空间地址,第二列是实际的代码字节,最后一列是汇编人类可读的形式。LDS 是从内存加载,ADIW 是加法,STS 是存回内存

这是较小的数据类型,具有预期的结果:

// int8_t bright - increment line - only load and store 1 byte
// 10 bytes of code

   bright += 1;
 18a:   80 91 02 01     lds r24, 0x0102
 18e:   8f 5f           subi    r24, 0xFF   ; 255
 190:   80 93 02 01     sts 0x0102, r24

请注意 SUBI 255 而不是添加 1 的奇怪之处——这是编译器开发人员的技巧。

所以你去吧,较小的数据类型会产生更小的代码,如你所料。你是对的!哦等等,你已经说过你不正确的地方......


比较函数调用

但是函数调用呢?analogWrite() 方法需要一个 int,因此如果需要,编译器将被迫创建一个转换

// int - needs no type conversion, can directly load value 
// from addresses 0x0102 and 0x0103 and call
// 16 bytes code

 // apply current light level
  analogWrite(13,bright);
 1b0:   20 91 02 01     lds r18, 0x0102
 1b4:   30 91 03 01     lds r19, 0x0103     

 1b8:   8d e0           ldi r24, 0x0D   ; 13
 1ba:   b9 01           movw    r22, r18
 1bc:   0e 94 87 02     call    0x50e   ; 0x50e <analogWrite>

LDI 正在加载常量,MOVW 正在移动变量以准备调用。

// int8_t - needs a type conversion before call
// 20 bytes code
 
  // apply current light level
  analogWrite(13,bright);
 1a0:   80 91 02 01     lds r24, 0x0102
 1a4:   28 2f           mov r18, r24
 1a6:   33 27           eor r19, r19
 1a8:   27 fd           sbrc    r18, 7
 1aa:   30 95           com r19
 
 1ac:   8d e0           ldi r24, 0x0D   ; 13
 1ae:   b9 01           movw    r22, r18
 1b0:   0e 94 76 02     call    0x4ec   ; 0x4ec <analogWrite>

无需了解类型转换的程序集即可看到效果。较小的数据类型产生了更多的代码。


那么这是什么意思呢?较小的数据类型既减少了代码大小又增加了代码大小。除非您可以在脑海中编译代码,否则您无法通过检查来解决这个问题,您必须尝试一下。

于 2013-08-13T08:47:18.653 回答
1

通常,您必须区分代码大小和数据大小。优化数据大小可能会增加您的代码大小(也会减慢速度),因为编译器需要将更多指令放入代码中,以便在各种可能的数据大小之间来回转换。

因此,作为一个经验法则:对于任何在数据中最多出现几次的值使用默认数据大小(例如“int”)。另一方面,如果您有大型数组,则设置最佳数据大小(例如“短”,如果该值保证在 -32768 .. 32767 范围内)可以大大减少应用程序在运行时的内存占用。

在您的情况下,如果您没有太多数据,请更多地关注优化代码大小:减少使用的库数量,并避免使用包装器等。

于 2013-08-12T17:15:45.737 回答
0

最大的内存消耗之一是浮点数(在 RAM 和 FLASH 中)。Ram 是因为类型大于整数,而 Flash 是因为 Arduino 没有浮点单元。因此,所有浮点运算都将导致更大的可执行文件。

还要注意使用库可能会链接许多不需要的东西,这些东西会消耗大量内存。

话虽如此:如果没有更多关于代码的详细信息,很难确定为什么会有如此大的内存占用。

于 2013-08-14T20:47:53.283 回答