контекст
Мы переносим код C, который был изначально скомпилирован с использованием 8-битного компилятора C для микроконтроллера PIC. Обычная идиома, которая использовалась для того, чтобы не допустить повторения нуля беззнаковых глобальных переменных (например, счетчиков ошибок), заключается в следующем:
if(~counter) counter++;
Битовый оператор здесь инвертирует все биты, и оператор верен, только если counter
он меньше максимального значения. Важно то, что это работает независимо от размера переменной.
проблема
Сейчас мы ориентируемся на 32-битный процессор ARM, используя GCC. Мы заметили, что один и тот же код дает разные результаты. Насколько мы можем судить, похоже, что операция побитового дополнения возвращает значение, которое отличается от ожидаемого. Чтобы воспроизвести это, мы компилируем в GCC:
uint8_t i = 0;
int sz;
sz = sizeof(i);
printf("Size of variable: %d\n", sz); // Size of variable: 1
sz = sizeof(~i);
printf("Size of result: %d\n", sz); // Size of result: 4
В первой строке вывода мы получаем то, что ожидаем: i
1 байт. Однако побитовое дополнение i
фактически составляет четыре байта, что вызывает проблему, потому что сравнение с этим сейчас не даст ожидаемых результатов. Например, если вы делаете (где i
правильно инициализирован uint8_t
):
if(~i) i++;
Мы увидим i
«обтекание» от 0xFF до 0x00. Это поведение отличается в GCC по сравнению с тем, когда он работал, как мы предполагали в предыдущем компиляторе и 8-битном микроконтроллере PIC.
Мы знаем, что мы можем решить эту проблему следующим образом:
if((uint8_t)~i) i++;
Или
if(i < 0xFF) i++;
Однако в обоих этих обходных путях размер переменной должен быть известен и подвержен ошибкам для разработчика программного обеспечения. Такого рода проверки верхних границ происходят по всей кодовой базе. Существует несколько размеров переменных (например, uint16_t
и unsigned char
т. Д.), И мы не ожидаем их изменения в другой работающей кодовой базе.
Вопрос
Является ли наше понимание проблемы правильным, и есть ли варианты решения этой проблемы, которые не требуют повторного посещения каждого случая, где мы использовали эту идиому? Верно ли наше предположение, что такая операция, как побитовое дополнение, должна возвращать результат того же размера, что и операнд? Кажется, что это сломается, в зависимости от архитектуры процессора. Я чувствую, что принимаю сумасшедшие таблетки, и С должен быть немного более портативным, чем этот. Опять же, наше понимание этого может быть неправильным.
На первый взгляд это может показаться не такой уж большой проблемой, но эта ранее работавшая идиома использовалась в сотнях локаций, и мы стремимся понять это, прежде чем приступить к дорогостоящим изменениям.
Примечание: здесь есть похожий, но не точный повторяющийся вопрос: побитовая операция над символом дает 32-битный результат
Я не видел реальной сути обсуждаемой там проблемы, а именно, размер результата побитового дополнения отличается от того, что передается оператору.