Препроцессор C или C , выбирающий блоки кода

#c #c-preprocessor #conditional-compilation

#c #c-препроцессор #условная компиляция

Вопрос:

Я пытаюсь написать программное обеспечение для производственного тестирования для встроенной системы, где я мог бы написать один тестовый скрипт (который в идеале является просто кодом C ), где части выполняются на главном компьютере, а часть — на ИУ (тестируемом устройстве).). Связь осуществляется через последовательный порт.

Одной из важных целей здесь является уменьшение размера кода на встроенной стороне, без снижения читаемости выходных данных теста. Итак, моя цель уровня 0, можно сказать, упражнение для разминки, состоит в том, чтобы иметь возможность написать что-то вроде этого:

 //TestScript.cpp
START_TESTS() 
...
 const unsigned pot1res = testPotentiometer(pot1);
 TEST_PRINT("Potentiometer 1 test result %u", pot1res);
 const unsigned pot2res = testPotentiometer(pot2);
 TEST_PRINT("Potentiometer 2 test result %u", pot2res);
...
END_TESTS()
  

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

  const unsigned pot1res = testPotentiometer(pot1);
 write_uart(123); //Unique id, perhaps from __COUNTER__
 write_uart(pot1res);
 const unsigned pot2res = testPotentiometer(pot2);
 write_uart(124); //Unique id, perhaps from __COUNTER__
 write_uart(pot2res);
  

и на хосте

 std::array gTestStrings = {
        ... other strings ....
    TestString{123, "Potentiometer 1 test result %u", unsigned_tag},
    TestString{124, "Potentiometer 2 test result %u", unsigned_tag},
         ... more strings ....
};
  

Целью последнего, конечно, является то, что программное обеспечение хоста просто прослушивает UART для уникальных идентификаторов, затем ищет необходимые параметры из gTestStrings , получает их и распечатывает сообщение в своем журнале тестирования. Обратите внимание, что строки полностью исчезли со встроенной стороны.

Встроенная сторона здесь, конечно, проста, просто определите TEST_PRINT макрос очевидным способом, а поддержка переменных и т.д. Не должна быть слишком сложной. Однако неясно, как определить хост-сторону, поскольку код между макросами должен полностью исчезнуть. Я почти уверен, что смогу правильно получить unsigned_tag s и т.д. С некоторыми шаблонами и т.д.

Приветствуется стандартный C 17, но при необходимости допускаются особенности GCC / Clang, препроцессор, очевидно, будет играть важную роль в этом и т.д. Синтаксис макросов, конечно, также может быть скорректирован при необходимости.

Комментарии:

1. Я не понимаю, как бы вы удалили const unsigned pot2res = testPotentiometer(pot2); строки? Что такое unsigned_tag ?

2. unsigned_tag (возможно, здесь неверный выбор имени) — это значение, которое идентифицирует тип. Т. Е. иногда результатом теста может быть, например, значение с плавающей точкой, и тогда у нас будет float_tag .

Ответ №1:

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


Пример псевдокода:

 #include <sstream>
#include <string>

class Mock
{
protected:
    // TODO : output values array / map - by - testID

public:
    unsigned readPot(int const testID)
    {
        // TODO : return value from the values array/map
    }

};

class ActualDevice
{
public:
    unsigned readPot(int const testID)
    {
        // TODO : write/read the device
    }

};

//DEVICE_IMPL must implement a method with this signature:
//unsigned readPot(int const testID)
template<typename DEVICE_IMPL>
void runTests()
{
    DEVICE_IMPL device;
    std::string testOutput;
    
    //TODO : substitute with for-loop to run all tests
    int testID = 1;
    {
        unsigned output = device.readPot(testID);
        std::stringstream accum;
        accum << "Test " << testID << " output = " << (int)output << std::endl;
        testOutput = accum.str();
        // TODO : do something with testOutput
    }
}

void testHost ()
{
    runTests<Mock>();
}


void testDevice()
{
    runTests<ActualDevice>();
}
  

Ответ №2:

 #ifdef __linux__ // or whatever you have there
#define START_TESTS()     std::array gTestStrings = {
#define END_TESTS()       };
#define TEST(expr, str)   TestString{__LINE__, str, unsigned_tag},
#else
#define START_TESTS()     {
#define END_TESTS()       }
#define TEST(expr, ...)   do{ 
    const auto _val = (expr); 
    write_uart(__LINE__); 
    write_uart(_val); 
} while(0)
#endif

int main() {
   START_TESTS()
      TEST(testPotentiometer(pot1), "Potentiometer 1 test result %u");
      TEST(testPotentiometer(pot2), "Potentiometer 2 test result %u");
   END_TESTS()
}
  

Мне не особенно нравится этот дизайн — он кажется недостаточно гибким для меня. Но такой код мог бы послужить вам шаблоном для написания чего-то лучшего. Я добавил выражение внутри TEST макроса — таким образом, его можно удалить на стороне хоста и использовать на стороне устройства.

Ответ №3:

Другим вариантом могут быть X-макросы:

pot_tests.h

 X(123, pot1, "Potentiometer 1 test result %u")
X(124, pot2, "Potentiometer 2 test result %u")
  

встроенный.c

 START_TESTS()
 ...
#define X(id, pot, text)                                 
    do {                                                 
        const unsigned potres = testPotentiometer(pot);  
        write_uart(id);                                  
        write_uart(potres);                              
        TEST_PRINT(text, potres);                        
    } while(0);
#include "pot_tests.h"
#undef X
 ...
END_TESTS()
  

host.cpp

 std::array gTestStrings = {
    ... other strings ....
    #define X(id, pot, text)  TestString{id, text, unsigned_tag},
    #include "pot_tests.h"
    #undef X
    ... more strings ....
};