C++数据类型及其对可执行文件大小的影响

C++ data-types, and their effects on executable size

本文关键字:文件大小 影响 可执行 数据类型 C++      更新时间:2023-10-16

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

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

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

此外,如果要求不过分;有没有人有一些技巧,或者链接到一些关于优化有限内存微控制器(如Arduino)的C++代码的信息?

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

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

帮我理解原因。。。

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

要了解发生了什么,您必须查看编译器生成的汇编代码。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

注意SUBI255而不是加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>

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


那么这意味着什么呢?较小的数据类型既减少了代码大小,又增加了代码大小。除非你能在脑子里编译代码,否则你无法通过检查来解决这个问题,你只能尝试一下。

首先,看看如何优化你的Arduino内存使用和优化Arduino的内存使用。此外,请注意节省RAM空间。

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

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

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

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

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

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