Типы данных C ++ и их влияние на размер исполняемого файла

Я в основном новичок в C ++, кроме попыток выучить язык более 10 лет назад и сдаться, потому что у меня не было проекта, чтобы мотивировать меня … В любом случае, я просто заявляю, что я довольно большое спасибо C ++, чтобы вы, ребята, знали мой текущий уровень знаний. Тем не менее, я довольно хорошо знаю Python и PHP. И так как оба эти языка имеют свободную типизацию, я не настолько знаком с влиянием, которое приведение типов в C ++ оказывает на размер исполняемого файла, если таковой имеется.

Я пишу программу Arduino, чтобы взять некоторые данные от пары ультразвуковых датчиков расстояния и применить данные к алгоритму сервоуправления. Никаких проблем с этим, но сейчас я пытаюсь оптимизировать свой код, так как я приближаюсь к пределу Arduino Micro в 28 672 байта. Моей первой мыслью было, по возможности, изменить мои типы данных на такие, как short int и char, ожидать, что они либо не дадут эффекта, либо немного уменьшить размер моего исполняемого файла. Я обнаружил, что после этих изменений размер исполняемого файла увеличился на несколько сотен байт.

Может ли кто-то с большим знанием C ++, чем я, любезно помочь мне понять причину этого, и почему я должен или даже не должен пытаться выбирать наименьшие возможные типы данных для моих переменных? Очевидно, результаты диктуют то, что я должен делаю здесь, но мне действительно нравится понимать «почему» за вещами, и после некоторого поиска в Google, я все еще придумал неуверенный.

Кроме того, если это не слишком много, чтобы спросить; У кого-нибудь есть советы или ссылка на информацию по оптимизации кода C ++ для микроконтроллеров с ограниченной памятью, таких как Arduino?

1

Решение

Вы спрашиваете много вещей, но на это можно ответить примером:

Я обнаружил, что после этих изменений размер исполняемого файла увеличился на несколько сотен байт.

… помогите мне понять причину этого …

В общем, вы не можете предсказать, будет ли меньший тип данных лучше или хуже, что продемонстрирует небольшой фрагмент кода ниже.

Чтобы увидеть, что происходит, вы должны посмотреть на ассемблерный код, созданный компилятором. В цепочке инструментов AVR есть компонент, который создает такой список, обычно файл .LSS. Я не думаю, что Arduino поддерживает это. Приведенные ниже списки сборок выполняются через Eclipse, который по умолчанию управляет расширенным списком.

Вот небольшой раздел программы мигания светодиодов, который можно использовать для демонстрации вашего замешательства. У него есть значение яркости, которое он устанавливает для светодиода в цикле:

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);

Чтобы продемонстрировать, яркий переменная изменяется между 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>

Не нужно разбираться в сборке для преобразования типов, чтобы увидеть эффект. Меньший тип данных произвел Больше код.


Так что это значит? Меньший тип данных уменьшает размер кода и увеличивает размер кода. Если вы не можете скомпилировать код в своей голове, вы не можете понять это путем проверки, вы просто должны попробовать это.

2

Другие решения

Как правило, вы должны различать размер кода и размер данных. Оптимизация размера данных, вероятно, увеличит размер вашего кода (а также замедлит работу), потому что компилятору нужно добавить в код больше инструкций для преобразования между различными возможными размерами данных и обратно.

Итак, как правило: используйте размер данных по умолчанию (например, «int») для любого значения, которое появляется в данных не чаще, чем несколько раз. С другой стороны, если у вас большие массивы, установка оптимального размера данных (например, «короткий», если значение гарантированно находится в диапазоне -32768 .. 32767) может значительно уменьшить объем памяти вашего приложения во время выполнения.

В вашем случае, когда у вас мало данных, сосредоточьтесь на оптимизации размера кода: уменьшите количество используемых библиотек, избегайте упаковщиков и т. Д.

1

Одним из крупнейших потребителей памяти являются числа с плавающей запятой (как в RAM, так и во FLASH). Ram, потому что типы больше целых и Flash, потому что Arduino не имеет единицы с плавающей запятой. Таким образом, все операции с плавающей запятой приведут к увеличению размера исполняемого файла.

Также позаботьтесь о том, чтобы использование библиотек могло связать множество ненужных вещей, которые занимают значительное количество вашей памяти.

Сказав это: без каких-либо подробностей о вашем коде довольно трудно определить, почему у вас такой большой объем памяти.

0
По вопросам рекламы [email protected]