Контекст
Ми переносимо код C, який спочатку був складений за допомогою 8-бітного компілятора С для мікроконтролера 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
т. Д.), І їх зміна в інакше працюючій кодовій базі не те, що ми з нетерпінням чекаємо.
Питання
Чи правильне наше розуміння проблеми, і чи є варіанти, щоб вирішити цю проблему, які не потребують повторного відвідування кожного випадку, коли ми використовували цю ідіому? Чи правильне наше припущення, що операція на зразок бітового доповнення повинна повертати результат, який має той самий розмір, що й операнд? Схоже, це зламається, залежно від архітектури процесора. Я відчуваю, що я приймаю божевільні таблетки і що С повинен бути трохи більш портативним, ніж цей. Знову ж, наше розуміння цього може бути неправильним.
З іншого боку, це може здатися не великою проблемою, але ця раніше працююча ідіома використовується у сотнях локацій, і ми готові зрозуміти це, перш ніж продовжувати дорогі зміни.
Примітка: Тут, схоже, схожий, але не точний повторюваний питання: побітна операція на char дає 32-бітний результат
Я не бачив фактичної суті проблеми, що обговорювалася там, а саме, розмір результату побітового доповнення відрізняється від того, що передається оператору.