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

Сегодня будем выводить иконку на черно-белый графический LCD — но это слишком простая задача. Потому что перед тем как её вывести, необходимо её нарисовать. Рисовать можно в Paint, потом использовать генератор, который переведет растровое изображение в код и использовать его для вывода на экран.

Но мы не ищем простых путей, поэтому иконку будем рисовать сами на С++ для CortexM4 микроконтроллера и сразу в ПЗУ, чтобы не зависеть от всех этих внешних программ, заодно и посмотрим как можно отловить ошибки в уже существующем коде (студентов), которые никто не заметил (даже PVS-Studio).

А еще некоторые компиляторы запрещают делать UB для кода исполняющегося во времени компиляции, поэтому можно отлавливать и UB. Например, мой IAR прекрасно ловит переполнения int. Но обо всем поподробнее.

Чтобы было просто — рисовать будем круг.

Как нарисовать круг

Итак, рисовать будем круг, и срисовываем его прямиком с Википедии — вот прямо отсюда.

На С++ от сутдентов это будет выглядеть примерно так:

Код круга
/*************************************************************************
* Function: drawCircle
*************************************************************************/
template<typename TFrameBuffer>
constexpr void DrawLibrary::drawCircle(TFrameBuffer& framebuffer, const tPoint leftUpperPoint,
                                        const tPoint rightLowerPoint, const size_t width)
{
   const  size_t sizeX= rightLowerPoint.coordinateX - leftUpperPoint.coordinateX;
   const  size_t sizeY = rightLowerPoint.coordinateY - leftUpperPoint.coordinateY;
   const size_t maxDiameter = sizeX < sizeY ? (sizeX) : (sizeY);
  
   size_t radius = maxDiameter / 2U;
   const size_t centerX = radius;
   const size_t centerY = radius;
   
   for (size_t i = 0U; i < width; ++i)
   {
      size_t y = radius;
      size_t x = 0;
      int32_t delta = 1 - radius * 2;
      int32_t error = 0;
      
      while (y >= x)
      {
         const size_t centerXPlusX = centerX + x;
         const size_t centerXPlusY = centerX + y;
         const size_t centerXMinusX = centerX - x;
         const size_t centerXMinusY = centerX - y;
         const size_t centerYPlusY = centerY + y;
         const size_t centerYPlusX = centerY + x;
         const size_t centerYMinusY = centerY - y;
         const size_t centerYMinusX = centerY - x;
   
         framebuffer.setDataInGivenPosition(centerXPlusX,centerYPlusY);
         framebuffer.setDataInGivenPosition(centerXPlusX,centerYMinusY);
         
         framebuffer.setDataInGivenPosition(centerXMinusX,centerYPlusY);
         framebuffer.setDataInGivenPosition(centerXMinusX,centerYMinusY);
         
         framebuffer.setDataInGivenPosition(centerXPlusY,centerYPlusX);
         framebuffer.setDataInGivenPosition(centerXPlusY,centerYMinusX);
         
         framebuffer.setDataInGivenPosition(centerXMinusY,centerYPlusX);
         framebuffer.setDataInGivenPosition(centerXMinusY,centerYMinusX);
        
         error = 2 * (delta + y) - 1;
         if ((delta < 0) && (error <= 0))
         {
            ++x;
            delta += 2 * x + 1;
         }
         else if ((delta > 0) && (error > 0))
         {
            --y;
            delta -= 2 * y + 1;
         }
         else
         {
            ++x;
            --y;
            delta += 2 * (x - y);
         }
      }
      --radius;
   };
}

В этом коде сразу минимум 2 ошибки, которые как будто-бы не влияют на работу функции, но на самом деле еще как влияют, но об этом немного позже. Так вот, одна из этих ошибок UB и вторая — выход за предел массива у буфера buffer. Найти такие ошибки трудно, и даже PVS-Studio обнаружил только UB.

Немного о функции, она рисует круг, вписанный в квадрат с координатами leftUpperPoint и rightLowerPoint, если это прямоугольник, то берется наименьшая из сторон. В строчке 10 как раз так задается диаметр. Остальное отлично описано в википедии.

Буфер в "ПЗУ"

Как вы заметили, функция рисования круга — constexpr — это означает, что рисовать она может во время компиляции. Но для того, чтобы она рисовала во время компиляции, необходимо, чтобы передаваемый buffer, тоже был доступен во время компиляции. Давайте быстренько напишем простейший такой буфер.

template <size_t XsizeInPixels, size_t YsizeInPixels>
class FrameBuffer
{
public:
    constexpr size_t getSize() const
    {
        return std::size(buffer);
    }
    static constexpr size_t getSizeX() 
    {
        return XsizeInPixels;
    }
    static constexpr size_t getSizeY() 
    {
        return YsizeInPixels;
    }
    
    constexpr void setDataInGivenPosition(const size_t x, const size_t y)
    {
      const size_t yPosition = y / 8U; 
      const size_t positionBitInByte = (y - (yPosition * 8U ));
      buffer[y * yPosition + x] |= (1U << positionBitInByte);
    }
    
private:
    uint8_t buffer[XsizeInPixels * YsizeInPixels / 8U] = {};    
};

В нем ничего такого интересного, кроме того, что он имеет constexpr конструктор и constexpr функцию setDataInGivenPosition т.е. формально мы можем создать этот буфер на этапе компиляции и нарисовать туда круг.

Без сомнения, если мы создадим constexpr объект буфера, то он попадет в ПЗУ.

constexpr FrameBuffer<10, 10> oBuffer; //всё - буфер в ПЗУ

Но нарисовать туда не получится, поскольку функция setDataInGivenPosition хоть и constexpr, но не константная и меняет состояние объекта и вызывать её у самого constexpr объекта нельзя — это и логично.

Иконка в ПЗУ и не очень в ПЗУ

Чтобы разрешить проблему, мы можем вызывать эту функцию при инициализации constexpr объекта. Поэтому давайте создадим класс иконка, которая будет просто кругом.

template <typename TBuffer, size_t width>
struct IconCircle
{
   constexpr IconCircle()
   {
      const tPoint leftUpperPoint = {0U, 0U};
      const tPoint rightLowerPoint = {getSizeX(), getSizeY()};
      cDrawLibrary::drawCircle(oFrameBuffer, leftUpperPoint, 
                               rightLowerPoint, width);
   }
   
   TBuffer oFrameBuffer;
   constexpr size_t getSizeX() const {return TBuffer::getSizeX();}
   constexpr size_t getSizeY() const {return TBuffer::getSizeY();}
};

Теперь в constexpr конструкторе мы рисуем круг прямо в oFrameBuffer во время компиляции и наш буфер попадает прямиком в ПЗУ, но это не точно, потому что компилятор может рассчитанные значения подставлять прямо в код, все зависит от того будете ли обращаться к буферу по его адресу или там в цикле по индексу или итератору.

А вот если решите вывести из него один байт, то компилятор просто подставит значение этого байта. Те значения, которые вы не используете, компилятор вообще может выкинуть, ну потому что они не нужны программе, например, решите вывести только 10 байт(ну не нужен вам весь круг почему-то) из иконки не обращаясь через итератор к массиву и, вуаля, будет только 10 этих значении прямо в коде, а остальных байт даже в ПЗУ не будет.

В общем, не суть важно, важно, что теперь мы можем создавать круг прямо в ПЗУ, и никакого кода не будет, максимум только массив байт буфера в ПЗУ.

Выглядит это так:

constexpr static size_t circleDiameter = 32U;
using tIconCircleBuffer = FrameBuffer<circleDiameter, circleDiameter>;
//Создаем иконку круг толщиной 3 пикселя прямиком в ПЗУ
constexpr IconCircle<tIconCircleBuffer, 3U> oIconSplashCircle{};

Весь код который мы до этого тут написали не попал в прошивку, остался только буфер с кругом в ПЗУ. Но, если что, такой код у нас не скомпилируется.... и все из-за тех 2 начальных ошибок, которые я показал в начале статьи.

А вот следующий очень даже скомпилится.

constexpr static size_t circleDiameter = 32U;
using tIconCircleBuffer = FrameBuffer<circleDiameter, circleDiameter>;
//Создаем иконку круг толщиной 3 пикселя прямиком в ОЗУ
IconCircle<tIconCircleBuffer, 3U> oIconCircle{};

Такой код уже будет работать, но не долго, пока программа не обратится к области важных данных, которые мы попортили. Кроме того, этот код генерит кучу написанного выше нами, кода функций, в том числе и рисование круга в буфер, который теперь будет находиться в ОЗУ — но возможно мы так и задумали, может быть мы хотим со временем поменять эту иконку.

Происходит это потому, что мы создали не константный объект oIconCircle, который по определению не может быть в ПЗУ, а значит не может и во время компиляции запустить метод рисования круга.

Ловим переполнение буфера

Переходим теперь к интересному, отлавливанию ошибок переполнения буфера компилятором. Итак, наша ошибка заключается в том, что мы неправильно определили координаты центра. В буфере массив по Х и Y начинается от 0 и до diameter — 1, а в функции, будет переполнение когда мы сложим 2 радиуса, см 29 строку и соответственно обратимся к буферу по координатам diameter, diameter, что явно за пределами массива.

template<typename TFrameBuffer>
static constexpr void DrawLibrary::drawCircle(TFrameBuffer& framebuffer, 
                                       const tPoint leftUpperPoint,
                                       const tPoint rightLowerPoint, 
                                       const size_t width)
{
   const  size_t sizeX= rightLowerPoint.coordinateX - leftUpperPoint.coordinateX;
   const  size_t sizeY= rightLowerPoint.coordinateY - leftUpperPoint.coordinateY;
   
   const size_t maxDiameter = sizeX < sizeY ? sizeX : sizeY;   
   
   size_t radius = maxDiameter / 2U; //самое просто отнять 1 от радиуса

   const size_t centerX = radius;
   const size_t centerY = radius;
   
   for (size_t i = 0U; i < width; ++i)
   {
      //размер буфера от 0 до diameter - 1, но мы задаем так, что
      //максимальное значение точки будет равно diameter == 2 * radius, 
      //что явно находится вне размера буфера.
      size_t y = radius;  
      size_t x = 0U;
      int32_t delta = 1 - radius * 2;
      int32_t error = 0;
      
      while (y >= x)
      {
         ...      
         //строка 29 Вот тут мы прибавляем к centerY + y == 2 * radius  
         framebuffer.setDataInGivenPosition(centerXPlusX,centerYPlusY);        
         framebuffer.setDataInGivenPosition(centerXPlusX,centerYMinusY);
         
         framebuffer.setDataInGivenPosition(centerXMinusX,centerYPlusY);
         framebuffer.setDataInGivenPosition(centerXMinusX,centerYMinusY);
         
         framebuffer.setDataInGivenPosition(centerXPlusY,centerYPlusX);
         framebuffer.setDataInGivenPosition(centerXPlusY,centerYMinusX);
         
         framebuffer.setDataInGivenPosition(centerXMinusY,centerYPlusX);
         framebuffer.setDataInGivenPosition(centerXMinusY,centerYMinusX);
        
         ...
   };
}

Но мы в нашем случае с иконкой в ОЗУ еще этого не обнаружили. А очень хотим обнаружить, но при этом, желаем чтобы иконка была в ОЗУ, а то вдруг мы потом решим поменять её и добавить к кругу рюшечек.

Давайте попробуем отловить это. Как мы помним, если бы мы рисовали кружок на этапе компиляции, то компилятор бы не смог такое УГ скомпилировать, потому что во время компиляции у него будет паника и данные не влезут в отведенный массив.

Таким образом, нам надо попробовать создать точно такой же объект во время компиляции, и если он создастся, то значит все с нашей функцией хорошо, а если нет — то беда.

Нужно учесть, что если мы создадим второй такой же объект он чисто теоретически отъест ПЗУ на размер буфера, хотя конечно реальный компилятор не даст этого сделать, так как этот объект не будет нигде использоваться. Но нам нужно, чтобы все было по правилам. Поэтому воспользуемся static_assert , где и попытаемся создать точно такой же объект.

constexpr static size_t circleDiameter = 32U;
using tIconCircleBuffer = FrameBuffer<circleDiameter, circleDiameter>;
using tIconCircle = IconCircle<tIconCircleBuffer, 3U>;
//Создаем иконку круг толщиной 3 пикселя в ОЗУ
tIconCircle oIconSplashCircle{};
//Пытаемся создать точно такой же объект во время компиляции, 
//если он создастся, то все хорошо
static_assert([](){constexpr tIconCircle oCircle{}; return true;}(), "Hi");

Вот теперь все хорошо, объекта, который создается в static_assert не будет в реальной жизни вообще, но попытка создать его во время компиляции будет, если там у нас приключится выход за пределы массива, то компилятор выдаст, что-то примерно такое:

<source>:202:50: error: array subscript value '144' is outside the bounds of array type 'uint8_t [128]' {aka 'unsigned char [128]'}

Можно лицезреть код здесь

Поправить это можно просто отняв 1 от радиуса

Тоже самое, но минус 1

Таким образом, мы можем отловить выход за границу буфера для нашей функции рисования круга. Конечно это только для конкретного экземпляра круга. Но мы можем во время компиляции перебрать все возможные комбинации. Компиляция конечно затянется, но зато будем уверены, что все хорошо на всем диапазоне значений. Это похоже на юнит тесты, но прямо во время компиляции.

Например, чтобы проверить, что функция setDataInGivenPosition не делает ничего запрещенного, можно написать такой тест

template<typename TBufferType, int maxValueI, int maxValueJ>
struct CompileTimeBufferSetDataTest
{
    static void check()
    {
       static_assert([]() 
       { 
         struct cFrameBufferSetDataInGivenPositionCompileTimeTest 
         {        
           constexpr cFrameBufferSetDataInGivenPositionCompileTimeTest(int i, 
                                                                       int j) 
           {  
             oBuffer.setDataInGivenPosition(i,j); 
             result = true; 
           }       
           TBufferType oBuffer; 
           bool result = true;
         };
         bool result = true;
         for (int i = 0; i < maxValueI; ++i)
         {
             for (int j = 0; j < maxValueJ; ++j)
             {
                cFrameBufferSetDataInGivenPositionCompileTimeTest oTest{i,j};
                result &= oTest.result; 
             }
         }         
         return result; 
       }(), "assert");      
    }    
};

Он просто пробегается по всем x и y от 0 до maxValueI, maxValueJ и вызывает функцию setDataInGivenPosition в конструкторе, и если она сделает что-то нехорошее, то объект не сможет создаться во время компиляции и компилятор выдаст ошибку.

Можно запихнуть в этот код какую-то логику, чтобы проверить работу функции более детально. А потом вызвать её где-нибудь в коде. Я вызвал ее так:

int main()
{
  static IconCircle<tIconCircleBuffer, 3U> oIconSplashCircle{};
 
  constexpr int maxValueI = 32; 
  constexpr int maxValueJ = 32; 
  //Check that with maxValueI there are no index out of bounds  
  CompileTimeBufferSetDataTest<tIconCircleBuffer,maxValueI,maxValueJ>::check(); 
}

IAR ловит UB

На самом деле, в godbolt компиляторы не смогли обнаружить UB, зато его обнаружил PVS-Studio — красавчик.

UB найденное PVS-Studio

<source>:140:1: warning: V1026 The 'delta' variable is incremented in the loop. Undefined behavior will occur in case of signed integer overflow.

А вот IAR не смог откомпилировать такой код, сработала наша заготовка со static_assert

Где находится UB
template<typename TFrameBuffer>
constexpr void DrawLibrary::drawCircle(TFrameBuffer& framebuffer, const tPoint leftUpperPoint,
                                        const tPoint rightLowerPoint, const size_t width)
{
   const  size_t sizeX= rightLowerPoint.coordinateX - leftUpperPoint.coordinateX;
   const  size_t sizeY = rightLowerPoint.coordinateY - leftUpperPoint.coordinateY;
   
   const size_t maxDiameter = sizeX < sizeY ? sizeX : sizeY;   
   size_t radius = maxDiameter / 2U - 1U; //беззнаковое целое

   const size_t centerX = radius;
   const size_t centerY = radius;
   
   for (size_t i = 0U; i < width; ++i)
   {
      size_t y = radius;   //беззнаковое целое
      size_t x = 0U;       //беззнаковое целое 
      int32_t delta = 1 - (int32_t)radius * 2; // вот тут порядок мы привел к (int32_t)
      int32_t error = 0;
      
      while (y >= x)
      {
         const size_t centerXPlusX = size_t(centerX + x);
         const size_t centerXPlusY = size_t(centerX + y);
         const size_t centerXMinusX = size_t(centerX - x);
         const size_t centerXMinusY = size_t(centerX - y);
         const size_t centerYPlusY = size_t(centerY + y);
         const size_t centerYPlusX = size_t(centerY + x);
         const size_t centerYMinusY = size_t(centerY - y);
         const size_t centerYMinusX = size_t(centerY - x);
   
         framebuffer.setDataInGivenPosition(centerXPlusX,centerYPlusY);
        
         framebuffer.setDataInGivenPosition(centerXPlusX,centerYMinusY);
         
         framebuffer.setDataInGivenPosition(centerXMinusX,centerYPlusY);
         framebuffer.setDataInGivenPosition(centerXMinusX,centerYMinusY);
         
         framebuffer.setDataInGivenPosition(centerXPlusY,centerYPlusX);
         framebuffer.setDataInGivenPosition(centerXPlusY,centerYMinusX);
         
         framebuffer.setDataInGivenPosition(centerXMinusY,centerYPlusX);
         framebuffer.setDataInGivenPosition(centerXMinusY,centerYMinusX);
        
         error = 2 * (delta + y) - 1; //тут вот тоже все странно
         if ((delta < 0) && (error <= 0))
         {
            ++x;
            delta += (int32_t)2 * x + (int32_t)1;
         }
         else if ((delta > 0) && (error >0))
         {
            --y;
            delta -= 2 * y + 1; // и тут
         }
         else
         {
            ++x;
            --y;
            delta += 2 * (x - y);  //А вот тут вообще не порядок, это UB
         }
      }
      --radius;
   };
}

Таким образом, в IAR кроме выхода за пределы массива можно отловить еще и все UB, которые есть в коде.

Резюме

Понаписал я много, но вы спросите, а иконка то в ПЗУ хоть правильно нарисовалась? Да, после всех доработок метода — и вот он пруф:

Код экспериментов

P.S

Кстати, если вы еще помните про UB вот в этой строчке

delta += 2 * (x - y);

Где x и y это беззнаковые целые, то студенты решили поправить это вот так:

delta += 2 * ((int32_t)x - (int32_t)y);

Как вы думаете, что произошло? Да UB ушел, но .... вместо круга получился Ромб. Вот такое оно бездумное преобразование типов.

Комментарии (30)


  1. oficsu
    03.12.2021 21:30
    +1

    Но нарисовать туда не получится, поскольку функция setDataInGivenPosition хоть и constexpr, но не константная и меняет состояние объекта и вызывать её у самого constexpr объекта нельзя - но это и логично.

    Разве эта проблема не решается возвратом буфера через возвращаемое значение, а не через входные параметры?


    1. lamerok Автор
      03.12.2021 21:43

      Да, рисование круга так и сделано в DrawLibrary::DrawCircle. Там входной параметр как раз буфер, но всё должно быть в самом constexpr обьекте. Т. Е нельзя создать буфер вне такого объекта и передать функцию объекта. Он должен быть частью этого объекта.


      1. oficsu
        03.12.2021 21:56
        +2

        Я это к тому, что если буфер не принимать по ссылке в DrawLibrary::DrawCircle, а вместо этого создать его в автоматической памяти внутри функции, он не будет константным и вы сможете его модифицировать, передав по ссылке в setDataInGivenPosition

        Мне в целом непонятна идея иметь out-параметры в функциях и считаю, что в пользу такого дизайна интерфейса нужна очень убедительная аргументация


        1. lamerok Автор
          03.12.2021 23:26

          Да согласен. Уже не помню зачем, так сделано было.

          P. S. Вспомнил, это же буфер иконки, она должна быть доступна для отрисовки. Общий метод рисования иконок подразумевает, что в метод передастся иконка, у которой есть свой буфер. Некоторые иконки трудно нарисовать математическими функциями и они всё таки генерятся из внешних тулов в массив. Но функция рисования иконок одна и ей нужен этот константный массив, в который уже ничего рисовать не надо.

          Но с рисованием круга согласен, надо убрать внутрь.


  1. technic93
    03.12.2021 23:35
    +2

    Кажется что без-знаковые тут только мешают. Какой правильный вариант в итоге я так и не понял :)


    1. lamerok Автор
      04.12.2021 09:47

      Да, все верно, правильно сразу задать x и y знаковые. Беззнаковые нужны только при установке точки в буфере, но только надо быть уверенным, что они беззнаковые - это как раз IAR компилятор и на этапе компиляции constexpr объекта отлавливает, потому что такое скомпилировать не может.


  1. Sergey_zx
    04.12.2021 05:19
    +2

    Теоретически оно конечно прикольно, спору нет.

    Но на практике генерировать более-менее сложные статические данные удобнее и проще внешней программой. Вызывать ее можно в make, или в скрипте сборки.

    Хотя мне часто в си для полного счастья в не хватало прагмы для вставки вывода внешней программы в компилируемый текст.


    1. lamerok Автор
      04.12.2021 10:22

      Согласен, внешние генерилки хорошая вещь, главное, чтобы генерила все правильно без всяких закладок и в нужном формате. Вот в С++ 20 завезут consteval, возможно чисто теоретически можно будет лазить к ресурсам в проекте (например в bmp файл) на этапе компиляции и самому оттуда вытаскивать все.


      1. technic93
        04.12.2021 18:12

        consteval уже есть, но файлы афаик читать всё равно нельзя.


        1. Reflector
          04.12.2021 20:01

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


    1. Reflector
      04.12.2021 19:59
      +1

      У меня подход с генерацией или конвертацией имеющихся данных на этапе компиляции используется во множестве мест, от форматирования текста до компиляции программ на ассме для модулей PIO входящих в состав Raspberry Pi Pico. Более удобная генерация при помощи внешних программ - это про какие-то исключительные случаи, лично я избавился от всех своих скриптов на C# которыми что-то генерил раньше.


      1. Sergey_zx
        04.12.2021 20:13

        Таблицу синусов для табличных вычислений, или битмапы из jpeg вы как делаете?

        И как вы делаете это кроссплатформенно и независимо от IDE?

        Например, у меня есть древний код для управления светодиодными табло. И мне совершенно без разницы куда его портить, ибо он совершенно платформеннонезависим.

        И знакогенератор, картинки, пиктограмки мне тоже без разницы, в каком виде иметь. Ибо они преобразуются к нужному виду внешними тулзами при сборке.


        1. Reflector
          04.12.2021 20:49
          +1

          Таблицы синусов генерятся элементарно, любого размера и точности, примерно как и круг в данной статье рисуется, т.е. будет класс с constexpr конструктором внутри которого массив и из конструктора вызываются constexpr методы заполняющие этот массив, который в итоге оказывается во флеше. Генерация битмапов из jpeg - это все же не совсем то, тут нужны достаточно сложные утилиты которые обычно уже существуют и писать их аналоги на С++ не имеет смысла, хотя легко можно представить, допустим, шрифт в виде массива в неком промежуточном формате, при этом можно указать компилятору, что хочу этот шрифт увеличенный вдвое, повернутый на 90гр., с обрезанными пустыми полями, кодировка чтоб была КОИ-8 и вот строка с текстом, найди в ней все уникальные символы и добавляй только их. Не так и сложно такое написать, явно проще компилятора. Но таблица синусов более показательный пример, потому что обычно генерятся разного рода таблицы и тогда проще написать класс на С++ который будет частью проекта, чем написать его на другом языке и таскать вместе с проектом.


          1. Sergey_zx
            04.12.2021 21:59

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

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


        1. mayorovp
          04.12.2021 22:03

          Э-э-э, а внешние утилиты — они тоже платформонезависимы? Да ещё и сборка без проблем на любой платформе идёт?


          1. fougasse
            04.12.2021 23:05

            Ну, в принципе, платформ для утилит очень ограниченное количество.

            Под target, всё-равно, нужно собирать кросс-компилятором. Это позволяет на host собрать в staging всё, что нужно, пропатчить исходники таблицами/константами и т.п.

            Конечно, если у вас утилита не собирается в вашей рабочей системе — это проблема. Но тут уже не до проблем с другими платформами.


          1. Sergey_zx
            04.12.2021 23:16

            По большей части да. Или это генератор каких то специфичных таблиц к которому тут же есть исходники для генератора. Очень часто это скрипт + линуксовые утилиты, тогда в винде через Cygwin. Всякие сложные аудио-видео конверторы и кодеки я делаю на QT, т. е. оно тоже кроссплатформенно средствами QT. Довольно часто текстовые, или cvs, или xml файлы которые нужно как то обработать и преобразовать.

            Но признаюсь, есть и непереносимые. Это мутные-старинные, и-или написаные на экзотичных языках,типа фортрана. Сделать их кроссплатформенными можно только полностью их переписав.

            Полагаю, половину, если не больше, можно переработать и в предлагаемом тут стиле. Но я тут следую первому правилу программиста - "работает, не трогай!"


  1. Sergey_zx
    04.12.2021 14:22

    Что бы гарантировано без закладок, можно и генерилки собирать в рамках того же проекта из исходников.


  1. NR_electronics
    04.12.2021 14:55

    У меня простой вопрос. Чем вообще хорош компилятор от IAR? Чем он лучше GCC и Keil?


    1. lamerok Автор
      04.12.2021 15:02
      +2

      Насколь я понимаю, осталось два вида компиляторов, Clang и GCC. И всё остальные компиляторы это просто надстройки, возможно со своей реализацией стандартной библитнки и всяких там доп фич и возможностями оптимизации т. Д.

      IAR начиная с 8 версии основан на Clang, со всеми вытекающими. Основное его преимущество, наличие Functional Safety версии

      https://www.iar.com/safety

      Что даёт возможность его использования для разработки Functional Safety устройств.


      1. NR_electronics
        05.12.2021 20:05

        Т.е. компилятор от IAR более "безопасен" для девайсов?
        От кейла такой же?


        1. lamerok Автор
          05.12.2021 21:24

          Насчёт более безопасен, не знаю. По моему опыту, что IAR 8.40.2, что IAR 8.40.3FS генерят абсолютно одинаковый код и имеют абсолютно одинаковые баги.

          Но без сертифицированного компилятора, вы не сможете получить сертификат безопасности для устройства для которого вы пишите софт. А таких я знаю пока только два: IAR и Green Hill

          https://www.ghs.com/products/compiler.html

          Он аж на SIL4 сертифицирован.


  1. alexzeed
    04.12.2021 18:25
    +1

    По тексту можно подумать, что ошибка с переполнением в коде с Википедии. Но это не так, там вообще не указан размер буфера куда рисовать. И исправление тоже странное, при уменьшении радиуса на 1 слева и вверху, либо справа и внизу, останутся пустые полоски в 1 пиксель. А по честному, нужно либо рисовать в буфер нечетного размера 2R+1 x 2R+1, либо рисовать окружность нецелого радиуса в точку с нецелыми координатами, чтобы она красиво легла в квадрат с четной стороной.


    1. lamerok Автор
      04.12.2021 19:09

      Да там действительно будет 1 пиксель пустых полосок, я написал, что это самое быстрое :), чтобы показать, что ошибка переполнения ушла.


  1. fk01
    05.12.2021 10:12

    Чтоб не устраивать переполнения буфера есть простой подход: исключить адресную арифметику над адресами. Работать только с регионами линейной памяти представимыми парой указателей или адресом и размером. Множество операций над регионом состоит только из функций усекающих регион, расширен регион быть никак не может. И обращение возможно только к какой-либо ячейке памяти внутри региона. Таковым регионом в C++ является std::span...

    Разумеется, где-то будет операция обращения к элементу по-индексу (operator[]), логику которого следует изменить, чтоб при обращении к несуществующему индексу было исключение, возврат ошибки, static_assert наконец. По-умолчанию std::span ошибки не детектирует.

    Хотя наверное могут быть регионы и не линейной памяти, а просто пара итераторов со случайным доступом. Это не принципиально.


    1. lamerok Автор
      05.12.2021 10:29

      Про span ещё не читал, C++20 пока в embedded нет. У меня вопрос.

      Разумеется, где-то будет операция обращения к элементу по-индексу (operator[]), логику которого следует изменить, чтоб при обращении к несуществующему индексу было исключение, возврат ошибки, static_assert наконец

      Кидать исключения в ембед не принято, слишком накладно, проверять выход за границу каждый раз при обращении тоже, а вот про static_assert не понял. Если, я индекс передаю как параметр оператора, то значит предполагается, что этот индекс можно передаваться не только в compile-time, но и runtime и значит static_assert там не проканает. Я же хочу использовать свой буфер в любом времени исполнении.

      Идея, описанная здесь заключается в том, что мы можем с имитировать рантайм поведение объекта в компайлтам, и проверить это на этапе компиляции. Ну скажем так, возможно это похоже на контракты, которые проверяются в компайлтам тайме.


      1. mctMaks
        06.12.2021 12:04

        C++20 пока в embedded нет

        немцы в Segger Embedded studio завезли поддержку, что странно. Так как они вроде делают свою "реплику" arm-toolchain (версию свою ставят поверх оригинальной, хотя может и правят что-то).

        обидно только то, что стандартную либу завезли только под clang.


        1. Reflector
          06.12.2021 16:19

          Не знаю как сейчас, но последний раз я ковырялся в SES 2 года назад и тогда там даже банального std::array и трейтов из С++11 не было, более того разрабы на форуме писали, что все эти stl отнюдь не приоритетное направление... В то же время до конца года должен выйти gcc 11 для ARM, а бетка gcc 10 с неплохой поддержкой С++20 еще в начале лета прошлого года была.


          1. mctMaks
            06.12.2021 18:33

            сейчас уже есть

            но рекомендовать прям яро не буду. двоякое отношение к этому продукту.


            1. Reflector
              06.12.2021 19:23

              На wiki написано, что C++ library добавила хедеры для exception, new, typeinfo, плюс обертки типа cmath для math.h. Я даже не поленился и установил SES, скачал ARM libcxx library и действительно хедеры для exception, new и typeinfo видит, а array, functional, type_traits и другие уже нет. И это типа прогресс, потому что STLport который использовался до этого был еще хуже. Ну и сама IDE похоже не особо поменялась, даже чтобы переименовать main.c в main.cpp мне пришлось удалять его из проекта, переименовывать вручную и добавлять заново...