Изменение типа одной переменной кардинально меняет размер компиляции

У меня есть набросок, содержащий следующий метод:

// Записывает нули на весь экран, очищая его:
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 байта. Это происходит из-за того, что компилятор распознает, что условие цикла никогда не будет достигнуто, и отсекает весь код позади, или это какое-то странное событие оптимизации? Я спрашиваю, потому что не могу сейчас протестировать скетч.

, 👍1

Обсуждение

Можете ли вы подтвердить это поведение без кода из дополнительных библиотек? Это может быть просто оптимизация с библиотекой (возможно, просто вызывается другая функция для uint_8, которая использует меньше места), @chrisl

это выражение: c < 128 * 8; приводит к переполнению переменной uint_8 'c'. Результатом является неопределенное поведение. При неопределенном поведении может произойти все, что угодно, @user3629249

@user3629249: Нет. Переполнение uint8_t совершенно безопасно и хорошо определено. Оно просто переходит через модуль 256. Только переполнения _знаковых_ целых чисел являются неопределенным поведением., @Edgar Bonet

@user3629249, даже если бы c был знаковым, что сделало бы переполнение проблемой, это сравнение не вызвало бы переполнение, а вот приращение c++ могло бы., @ilkkachu


1 ответ


Лучший ответ:

2

Не имея возможности доказать это (поскольку у меня нет ваших библиотек, а вы привели лишь небольшую часть наброска), я думаю, что происходит следующее:

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