Главная > AVR > Немного о пользе явного указания типа констант

Немного о пользе явного указания типа констант

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

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

Пояснить феномен можно на немудреном синтетическом примере. Напишем функцию, которая считает количество единиц в тридцатидвухбитном числе:

uint8_t count_ones(uint32_t value)
{
    uint8_t i,ones_count;

    ones_count=0;

    for (i=0; i<32; i++)
    {
        if (value & (1<<i))
        {
            ones_count++;
        }
    }

    return ones_count;
}

Если скомпилировать ее в GCC (MinGW), она будет работать совершенно прекрасно. Например, если передать ей значение 0xF70000F7, она исправно вернет 14. А вот если для разнообразия скомпилировать ровно тот же код в AVR-GCC, на тех же входных данных результат будет равен восьми. Почему?

Дело в константе «1». Согласно правилам, принятым в Си, разрядность константы равна разрядности типа int. В то же время, размер int гуляет от платформы к платформе. На компьютере он имеет разрядность 32 (сейчас чаще уже 64) бита, а в том же AVR GCC — 16 бит. Потому во втором случае и результат выражения (1<<i) будет иметь разрядность 16 бит со всеми вытекающими последствиями (переполнение вместо корректного анализа всех разрядов входного числа). Починить функцию можно, всего лишь явно указав тип константы:

uint8_t count_ones(uint32_t value)
{
    uint8_t i,ones_count;

    ones_count=0;

    for (i=0; i<32; i++)
    {
        if (value & (1UL<<i))
        {
            ones_count++;
        }
    }

    return ones_count;
}

Такая реализация работает корректно везде.

Думаю, на приведенном примере большинство читателей почти сразу догадалось, в чем дело. Однако в более сложном коде такой момент недолго и проглядеть.

Рубрики:AVR
  1. 26/07/2015 в 23:02

    Я немножко добавлю. Извините, если что!

    У avr-gcc размер int равен 16 бит. По умолчанию это именно так и есть.
    Но не все знают, что у компилятора есть опция -mint8 , которая позволяет изменить размер int на 8 бит.
    Нечто подобное я также видел в настройках CodeVision и IAR, когда сидел под Виндой.
    Я не буду здесь расписывать про -mint8, поскольку теперь вы знаете это «волшебное» слово и без меня сможете нагуглить информацию.

    Уважаемый, YS!

    Не важно, что ты падаешь. Важно, что ты каждый раз поднимешься.
    Не важно, что ты совершаешь (глупые) ошибки, над которыми могут посмеяться окружающие. Смеются лишь те, кто не совершал подобных ошибок. Важно, что совершая ошибки, ты набираешься опыта. Это главное!

    Продолжайте публиковать статьи, YS!
    Я с удовольствием читаю ваши публикации.

    • YS
      26/07/2015 в 23:54

      Да, у GCC есть много интересных опций. Порой мне кажется, что первой программой, которая осознает себя и станет полноценным ИИ, будет именно GCC. 🙂

      Спасибо за добрые слова. Рад, что мой блог еще читают. 🙂 Сейчас я стал реже публиковать статьи потому, что устроился на работу на полный день, но, я надеюсь, у меня все же будут появляться темы для публикаций.

  2. shads
    16/07/2015 в 23:23

    Я тоже как то уже запинался об этот ньюанс )))))
    http://radiokot.ru/forum/viewtopic.php?p=1363223#p1363223

    • YS
      17/07/2015 в 11:00

      В принципе, я бы не сказал, что это нюанс — про разрядность типов и констант ясно писано в документации на компилятор. 🙂 Но конечно, момент, который легко пропустить. Мне это стоило полутора дней раздумий и вдумчивого изучения кода. 🙂

      Я, кстати, поначалу грешил на volatile — у меня там массив, который используется в прерывании.

      • GrAnd
        09/08/2015 в 16:25

        .. вот для таких случаев и придумали правила MISRA. Там кстати есть отдельный параграф посвященный побитовым операциям

  1. No trackbacks yet.

Ответить на zhevak Отменить ответ