"Одноразовая" инструкция 🙂

Бывает так, что какое-то действие в программе, надо выполнить ,по условию ,только один раз. Ну ничего, норм. Заводим флаг, выполняем действие , сбрасываем флаг. Затем всё остальноё время продолжаем проверять этот флаг, хотя , это действие уже никогда выполнять не будем.
Например:


void PrintNum(uint8_t num)
{
  static bool f = true;

  if (f)             //  <-----------  можно ли избежать "пожизненной",

  {                  //и, после первого раза уже "не нужной" проверки?

    Serial.println("===== Превышение уровня чего-то там!!! ===========");
                                     //вывести строку один раз, при
    f = false;                       //первом вызове программы
  }
  Serial.print( " Время  ");
  Serial.print( millis());
  Serial.print(" мс     уровень...");
  Serial.println(num);
}


void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(0));
}

void loop() {
  uint8_t number;
  number = (uint8_t)random(255);
  if (number > 200)
    PrintNum(number);//прогамма вызывается, если превышено
                     // пороговое значение
  delay(500);
}



Вопрос:
А как обойтись без этого “досадного” действия?
Может, на более продвинутых процах это решено?

В общем случае, на Си - только так, как вы написали.

Проверка не занимает много времени, ничего страшного в этой проверке нет.

Но вы можете чутьчуть помочь компилятору, прямо указав ему, что условие скорее всего ложно:

int function(int x) {

 static int flag = 0;
  if (unlikely(flag == 0)) {
    // Делаем наше действие, которое надо сделать один раз
    flag = 1;
  }
  return 0;
}

Что такое unlikely?

Это макрос. У него есть братик - likely().

Расписать их следует самому, потому, что например , на ESP32 среда ардуино обнуляет эти макросы безо всякой причины.

#undef likely
#undef unlikely
#define unlikely(_X)   __builtin_expect(!!(_X), 0)
#define likely(_X)     __builtin_expect(!!(_X), 1)

Что происходит: компилятор понимает, что условие if( flag == 0) - маловероятное (unlikely), поэтому при компиляции кода, компилятор переставит код так, чтобы он исполнялся быстрее.

Ну и еще самое простое: уйти в другой бесконечный цикл (while(1) { // работа основного цикла }). :slight_smile:

можно через указатели на функции. Тогда роль флага будет выполнять указатель на функцию:

например, у вас есть указатель на функцию func_t function. Указывает на функцию function_with_flag() .

Пользовательский код:

(*function)()

Функция:

int function_with_flag() {

 что то там делаем, флага у нас вообще теперь нет, не нужен он
 
  function = function_without_flag;  // слекующий вызов function() вызовет уже функцию другую.
}

Сектч:

function();  // вызовет первую функцию
function(); // вызовет уже другую

Вот я тоже об этом подумал, но программист я не настоящий ( :sweat_smile: ), оформить не смог )))

Через likely красивше :slight_smile:

Вопрос больше академический.

Заниматься такой оптимизацией только если для этого действительно есть причины (т.е. реально не хватает производительности).

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

// ============================================
// 1. ОБЪЯВЛЕНИЕ ТИПА И УКАЗАТЕЛЯ
// ============================================

// Тип: функция, принимает uint8_t, ничего не возвращает
typedef void (*PrintFunc_t)(uint8_t);

// ============================================
// 2. ПРОТОТИПЫ ФУНКЦИЙ (ДОЛЖНЫ БЫТЬ ДО ИСПОЛЬЗОВАНИЯ!)
// ============================================

void PrintWithHeader(uint8_t num);
void PrintWithoutHeader(uint8_t num);

// Теперь компилятор знает о существовании этих функций!
// ГЛОБАЛЬНЫЙ указатель на функцию (хранится в SRAM)
PrintFunc_t g_print_func = PrintWithHeader; // OK, функция объявлена

// ============================================
// 3. ГЛАВНАЯ ФУНКЦИЯ-ДИСПЕТЧЕР
// ============================================

void PrintNum(uint8_t num) {
    // ВСЁ! Просто вызываем функцию по указателю.
    // Никаких if, никаких проверок!
    g_print_func(num);
}

// ============================================
// 4. ПЕРВАЯ ВЕРСИЯ (с заголовком)
// ============================================

void PrintWithHeader(uint8_t num) {
    // Печатаем заголовок (это случится только один раз!)
    Serial.println("===== Превышение уровня чего-то там!!! ===========");
    
    // КЛЮЧЕВОЙ МОМЕНТ: перенаправляем указатель на ВТОРУЮ функцию
    // Теперь g_print_func указывает на PrintWithoutHeader
    g_print_func = PrintWithoutHeader;
    
    // Вызываем вторую функцию, чтобы напечатать данные
    // (избегаем дублирования кода)
    PrintWithoutHeader(num);
}

// ============================================
// 5. ВТОРАЯ ВЕРСИЯ (только данные)
// ============================================

void PrintWithoutHeader(uint8_t num) {
    Serial.print(" Время  ");
    Serial.print(millis());
    Serial.print(" мс     уровень...");
    Serial.println(num);
}

// ============================================
// 6. СТАНДАРТНЫЕ ФУНКЦИИ ARDUINO
// ============================================

void setup() {
    Serial.begin(115200);
    randomSeed(analogRead(0));
    
    // Убеждаемся, что указатель указывает на ПЕРВУЮ функцию
    // (хотя это уже сделано при инициализации)
    g_print_func = PrintWithHeader;
}

void loop() {
    uint8_t number = (uint8_t)random(255);
    
    if (number > 200) {
        // Вызываем функцию-диспетчер
        // Внутри неё НЕТ проверок!
        PrintNum(number);
    }
    
    delay(500);
}

но лично мне нравится через if, постоянно проверять условия, и писать медленный код…)))

Ctrl+C/Ctrl+V это не “писАние”, а другое ударение :slightly_smiling_face:(о, и смайлики поменялись)

толстый вы все врете!)))
я вот сейчас задумался а быстрее ли…
это все проклятый ассемблер которого не знаю, и к которому все сводится…
по идее быстрее… проклятый ассемблер, проклятые такты…)))

Это да. Но всё равно , проверки не избежать.

ну а что там, несколько тактов процессора всего-навсего.

Т.е. надо иметь два одинаковых цикла? А флешь не вырастет в 2 раза?))

Ну, тоже что и с советом @BOOM-a. Надо иметь две , почти одинаковые функции?

Ага. Ну, они не будут прям уж одинаковыми. В одной будет hotpath, а во второй - условие и опять же вызов hotpath из первой функции. Функция с проверкой будет маленькая, а основная - толстенькая

Я бы оставил так, как в первом сообщении написано: и читаемо через год и просто.

Ну, это почти то же, о чём @vvb333007 написал, если я правильно понял.

Ну, об этом уже @vvb333007 написал же…

Ну, вопрос больше теоретический, хотя, иногда и несколько тактов это много

Надо все такое, что только один раз выполняется выносить до основного цикла (так сказать в setup() ). Тогда и вопросов подобных не будет :slight_smile:

Да, скорее это это так. Для Уно/Нано вряд ли чего лучше придумаешь.

Интересно, может, на “крутых” процессорах есть какое-нибудь аппаратное решение? Например, особый тип инструкций, которые выполняются только один раз. А потом уже читаются как “NOP”… Мало ли чего придумать можно.
Ведь необходимость выполнить что-то только один раз довольно часто бывает