Изменение типа одной переменной кардинально меняет размер компиляции
У меня есть набросок, содержащий следующий метод:
// Записывает нули на весь экран, очищая его:
void clearScreen(uint8_t val) {
setDrawArea(0x00, 0x7f, 0x00, 0x07); // полный экран
SSD1306.ssd1306_send_data_start();
for (int c = 0; c < 128 * 8; c++) {
SSD1306.ssd1306_send_byte(val);
}
SSD1306.ssd1306_send_data_stop();
}
Весь скетч компилируется с размером 6730 байт. Во время рефакторинга кода я по ошибке изменил "c" на uint8_t. После этого я скомпилировал в 4124 байта. Это происходит из-за того, что компилятор распознает, что условие цикла никогда не будет достигнуто, и отсекает весь код позади, или это какое-то странное событие оптимизации? Я спрашиваю, потому что не могу сейчас протестировать скетч.
@needfulthing, 👍1
Обсуждение1 ответ
Лучший ответ:
Не имея возможности доказать это (поскольку у меня нет ваших библиотек, а вы привели лишь небольшую часть наброска), я думаю, что происходит следующее:
user3629249 наполовину прав. Если изменить c
на тип uint8_t
, у него будет только 8 бит (отсюда и название), поэтому его максимальное значение будет 255. Когда вы добавите к этому значению 1, переменная переполнится и вернется к 0. Это нормальное поведение при переполнении, как при использовании регистров таймера Arduinos. (Здесь нет неопределенного поведения). Поэтому максимальное значение, которого может достичь c
, равно 255.
При сравнении с большим литералом значение c
повышается до правильного типа, так что два значения можно сравнивать. Но переменная, которая находится на максимуме 255, всегда меньше, чем 128*8. Так что у вас здесь бесконечный цикл. Компилятор может это увидеть и не включает функции из остальной части clearScreen()
, так как это недостижимый код.
В целом ваш код не будет работать так, как задумано с uint8_t
. Вам нужен больший тип, который может содержать весь диапазон, с которым вы работаете. И эта оптимизация не странная, а очень разумная, поскольку в этом случае вам действительно не нужна остальная часть clearScreen()
.
Обратите внимание, что _любой код_, следующий за вызовом clearScreen()
, также недоступен. Компилятор может удалять гораздо больше, чем ssd1306_send_data_stop()
., @Edgar Bonet
Преобразует ли компилятор 128*8 в 8-битное значение? 128*8=1024; что равно 0 при преобразовании/усечении до байта. Поскольку это ноль, цикл for никогда не будет выполнен, поэтому ssd1306_send_byte никогда не вызывается. Компилятор знает об этом во время компиляции, поэтому он может удалить цикл for. А если функция send_byte больше нигде не вызывается, он может удалить всю функцию, радикально сократив размер., @Gerben
@Gerben, если я не ошибаюсь, все операции повышают операнды как минимум до int
, и в любом случае, недекорированная константа - это int
, так что это 1024, и c
повышается для соответствия. Просто uint8_t
никогда не может достичь такого значения, поэтому условие всегда истинно., @ilkkachu
Это соответствует моим мыслям - похоже, я недооценил компилятор. За этим вызовом метода стоит значительная часть кода, и я совершенно уверен, что компилятор понимает, что условие "for" никогда не будет ложным, и отсекает все, что находится за ним. Боюсь, я привык к более высоким языкам, ожидая какого-то предупреждения компилятора в таком случае. Кстати, хорошие мысли о проблеме преобразования., @needfulthing
@needfulthing: По поводу «ожидания какого-то предупреждения компилятора»: компилятор _предупреждает_ вас, что «_сравнение всегда истинно из-за ограниченного диапазона типов данных_», но только если вы [установите «Предупреждения компилятора» на «Все» в настройках IDE](https://stackoverflow.com/a/45422879). Существует [открытая проблема](https://github.com/arduino/Arduino/issues/4184) с просьбой сделать это значением по умолчанию, но разработчики Arduino сопротивляются, так как боятся, что предупреждения могут «отпугнуть новичков»., @Edgar Bonet
- Будет ли .ino-скетч ардуино компилироваться непосредственно на GCC-AVR?
- Использование std::list в программировании Arduino
- Управление сервоприводом с помощью ATtiny13A
- Оптимизация кода для ATtiny10
- Как уменьшить использование глобальных переменных? Attiny85
- помогите скомпилировать код для проекта флоры
- Как установить приложение + его конфигурацию на множество однотипных устройств? (ESP32)
- C++ против языка Arduino?
Можете ли вы подтвердить это поведение без кода из дополнительных библиотек? Это может быть просто оптимизация с библиотекой (возможно, просто вызывается другая функция для
uint_8
, которая использует меньше места), @chrislэто выражение:
c < 128 * 8;
приводит к переполнению переменнойuint_8
'c'. Результатом является неопределенное поведение. При неопределенном поведении может произойти все, что угодно, @user3629249@user3629249: Нет. Переполнение
uint8_t
совершенно безопасно и хорошо определено. Оно просто переходит через модуль 256. Только переполнения _знаковых_ целых чисел являются неопределенным поведением., @Edgar Bonet@user3629249, даже если бы
c
был знаковым, что сделало бы переполнение проблемой, это сравнение не вызвало бы переполнение, а вот приращениеc++
могло бы., @ilkkachu