Вопрос по warning:

Вопрос “детский”, потому в песочнице.

Итак, есть некий код, например

#define EXAMPLE1
#define EXAMPLE2

void setup() {
  Serial.begin(9600);
}

void loop() {
#if defined(EXAMPLE1) || defined(EXAMPLE2)
  Serial.println("yes");
#else
  Serial.println("no");
#endif
}

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

#if defined(EXAMPLE1) || defined(EXAMPLE2)
......
#endif

С одной стороны ну и что? А с другой стороны может добавиться еще какой-то дефайн, и по всему проекту нужно будет искать и дополнять условия

#define EXAMPLE1
#define EXAMPLE2
#define EXAMPLE3

void setup() {
  Serial.begin(9600);
}

void loop() {
#if defined(EXAMPLE1) || defined(EXAMPLE2) || defined(EXAMPLE3)
  Serial.println("yes");
#else
  Serial.println("no");
#endif
}

Где-то что-то можно упустить, а потом словить какой-нибудь глюк )))
Собственно, для решения проблемы сделал так

#define EXAMPLE1
#define EXAMPLE2
#define EXAMPLE3

#define THREE defined(EXAMPLE1) || defined(EXAMPLE2) || defined(EXAMPLE3)

void setup() {
  Serial.begin(9600);
}

void loop() {
#if THREE
  Serial.println("yes");
#else
  Serial.println("no");
#endif
}

Все работает, vs code никаких предупреждений не выдает, можно успокоиться ))

Но тут что-то дернуло меня скомпилировать этот же код в Arduino IDE, и тут мне было выдано

/mnt/B6F0D77CF0D74173/_Documents/Vladimir/YandexDisk/Arduino/_temp/test1/test1.ino:12:5: warning: this use of "defined" may not be portable [-Wexpansion-to-defined]
 #if THREE
     ^~~

Собственно, сразу возникли вопросы:

Во-первых, а действительно, можно ли так делать? А то вдруг… ))

Во-вторых, почему же vs code мне этих предупреждний не показывает? В настройках выбрано Log Level: verbose, собственно, более подробных вариантов нету.

Ну и в третьих, а как заставить vs code выдавать все предупреждения? В Ардуино IDE уровень verbose соответствет настройке Сообщения компилятора: Подробнее, но там есть еще уровень Все, хотелось бы такой же иметь и в vs code.

А то до смешного - среди прочих предупреждений в давно вроде бы отлаженной библиотеке вдруг указало на строку типа

result = result = true;

)))

Портируемый вариант:

#define EXAMPLE1
#define EXAMPLE2
#define EXAMPLE3

#if defined(EXAMPLE1) || defined(EXAMPLE2) || defined(EXAMPLE3)
#define THREE
#endif

....

#if defined(THREE)

1 лайк

Действительно, так портируется ))

Но не работает. Даже если закомментировать все три дефайна, в монитор выдает сплошниые yes ))

Как раз здесь и можно нарваться на ту самую непортируемость, т.к. Вы её только спрятали внешне. Безопасный вариант вот такой:

#define EXAMPLE1
#define EXAMPLE2
#define EXAMPLE3

#if defined(EXAMPLE1) || defined(EXAMPLE2) || defined(EXAMPLE3)
   #define THREE 1
#else
   #define THREE 0
#endif

....

#if  THREE

Хотя, прваильно, конечно, врубать опцию C++17 и использовать if constexpr.

1 лайк

А ведь логично, чьорд возьми, мог бы и сам додуматься, но … )))

Это если только для себя, на вынос может не прокатить ))

Не знаю, вроде уж 24-ый год. Пора бы 17-ый не считать супер-новинкой :slight_smile:

Это ж Ардуино, среда для домохозяек ))

А в чем она состоит, не подскажете?

#if defined(XXX)

поддерживается не на всех платформах?

если это серьезно, то покажи код. Вроде нечему там “не работать”

Проехали, был не внимателен, прошу прощения. Работает ))

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

Если коротко, в формате отписки, то можно сказать так: не стоит использовать унарный оператор define внутри развёртываемых макросов. Вот, что об этом пишет Стандарт языка Си (выделения мои):

Prior to evaluation, macro invocations in the list of preprocessing tokens that will become the controlling constant expression are replaced (except for those macro names modifed by the defined unary operator), just as in normal text. If the token defined is generated as a result of this replacement process …, the behavior is undefned.

Собственно, на этом можно и закончить. Как видим, оператор define “особенный” и при использовании в макросах может взывать undefined behavior. А если оно undefined, то имеет право быть реализовано как Бог положит на душу разработчику реализации, а, значит, ни грамма не portable.

Но, мне бы хотелось показать на примере неожиданный “привет” от оператора define и его особенностей, возникающий на ровном месте и вызывающий отпадание челюсти, когда видишь его впервые. Давайте посмотрим пример (плюём на варнинги):

Пример 1
// Полезная книга про препроцессор: https://gcc.gnu.org/onlinedocs/gcc-4.2.4/cpp.pdf
//

#define A

#define MA defined(A)
#define MB defined(B)

void setup(void) {
   Serial.begin(9600);
   Serial.println("The fun begins!");

   #if MA
      Serial.println("MA");
   #else
      Serial.println("NOT MA");
   #endif

   #if MB
      Serial.println("MB");
   #else
      Serial.println("NOT MB");
   #endif

}

void loop(void) {}

и его

Результат
The fun begins!
MA
NOT MB

Ну, что, всё, вроде по уму … как и ожидалось, никаких “приветов”. Но мне не нравится, что я определил два практически одинаковых макроса MA и MB. А ну как их сто два потребуется?

Давайте-ка определим их одним макросом с параметром. Ну, что смотрим:

Пример 2
// Полезная книга про препроцессор: https://gcc.gnu.org/onlinedocs/gcc-4.2.4/cpp.pdf
//

#define A

#define M(x) defined(x)

void setup(void) {
   Serial.begin(9600);
   Serial.println("The fun begins!");

   #if M(A)
      Serial.println("M(A)");
   #else
      Serial.println("NOT M(A)");
   #endif

   #if M(B)
      Serial.println("M(B)");
   #else
      Serial.println("NOT M(B)");
   #endif

}

void loop(void) {}

и его

Результат
D:\Soft\kaka\kaka.ino:12:11: error: operator "defined" requires an identifier
    #if M(A)
           ^

exit status 1

Compilation error: operator "defined" requires an identifier

Опаньки! А что случилось? Да, ничего, просто дальше веселее!

Давайте выбросим из этого кода строки №№ 12-16 (относящиеся к определённому A), а оставим только работу с неопределённым B (не буду приводить код, просто удалите строки 12-16). И … на глазах изумлённой публики, всё нормально и правильно работает! Ну, уже с привычным варнингом, конечно.

Итак, что вот это сейчас было?

Макросы в Си-препроцессоре не разворачиваются внутри макросов. Именно поэтому, нельзя, например, просто так нагло написать рекурсивный макрос - он не развернётся. А оператор define “в душе макрос” и потому … приплыли, если он встречается внутри другого макроса, он может не “развернуться”, что мы и наблюдаем. Когда ему подсовывали неопределённый B он таки соизволил понять, что надо быть false и не выпендриваться. А когда давали определенный A - не сумел, т.к. А - определённый макрос, который тоже нуждается в развёртывании, а макросы не разворачиваются во время развёртывания другого макроса!

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

В общем, как было сказано в статье “Настоящие программисты не используют Паскаль”, сишный препроцессор - вещь для настоящих мужчин. В его дебри без крайней нужды лучше не лезть – там такое водится! Как там у Высоцкого было:

В заколдованных болотах там кикиморы живут —
Защекочут до икоты и на дно уволокут.
Будь ты пеший, будь ты конный — заграбастают,
А уж лешие так по лесу и шастают.

Ну, вот, не уверен, что стало понятнее, но объяснил как сумел :frowning:

7 лайков

Кажется, стало, спасибо ))

Теперь бы еще послушать знатоков vs code по третьему вопросу - а как же все таки заставить его выдавать все-все-все предупреждения?

Говорила мне мама, не вляпайся в Си, сынок. А я не послушалса. Пидец, короче.

3 лайка

Проблемы надуманы зачастую. Аккуратненько, попроще, и всё будет хорошо.)

2 лайка

Это ответ на то что ИИ еще долго не заменит человека пишущего на Си. Но правда в том что рабочих мест в мире на эту должность станет мало.

«Поцан пожыл, поцан знаит» (с)

Пздц, как же сложно было эту строку написать )))

2 лайка

И снова я со своими ворнингами ))

Код:

Спойлер
#include <shButton.h>

#define BTN_1_PIN 4
#define BTN_2_PIN 6
#define BTN_3_PIN -1

class ButtonGroup
{
private:
  shButton *buttons[3] = {NULL, NULL, NULL};

  shButton *btn_1 = NULL;
  shButton *btn_2 = NULL;
  shButton *btn_3 = NULL;

  bool isValid(uint8_t _btn)
  {
    bool result = false;

    if (buttons != NULL && _btn < 3)
    {
      result = buttons[_btn] != NULL;
    }

    return result;
  }

public:
  ButtonGroup() {}

  void init()
  {
#if (BTN_1_PIN >= 0)
    btn_1 = (shButton *)calloc(1, sizeof(shButton));
    if (btn_1 != NULL)
    {
      btn_1 = &(shButton){BTN_1_PIN};
      buttons[0] = btn_1;
    }
#endif

#if (BTN_2_PIN >= 0)
    btn_2 = (shButton *)calloc(1, sizeof(shButton));
    if (btn_2 != NULL)
    {
      btn_2 = &(shButton){BTN_2_PIN};
      buttons[1] = btn_2;
    }
#endif

#if (BTN_3_PIN >= 0)
    btn_3 = (shButton *)calloc(1, sizeof(shButton));
    if (btn_3 != NULL)
    {
      btn_3 = &(shButton){BTN_3_PIN};
      buttons[2] = btn_3;
    }
#endif
  }

  uint8_t getButtonState(uint8_t _btn)
  {
    uint8_t result = BTN_RELEASED;

    if (isValid(_btn))
    {
      result = buttons[_btn]->getButtonState();
    }

    return result;
  }
};

ButtonGroup buts;

void setup()
{
  Serial.begin(9600);
  buts.init();
}

void loop()
{
  switch (buts.getButtonState(0))
  {
  case BTN_DOWN:
    Serial.println("button1 - click");
    break;
  case BTN_DBLCLICK:
    Serial.println("button1 - dblclick");
    break;
  }
  switch (buts.getButtonState(1))
  {
  case BTN_DOWN:
    Serial.println("button2 - click");
    break;
  case BTN_DBLCLICK:
    Serial.println("button3 - dblclick");
    break;
  }
  switch (buts.getButtonState(2))
  {
  case BTN_DOWN:
    Serial.println("button3 - click");
    break;
  case BTN_DBLCLICK:
    Serial.println("button3 - dblclick");
    break;
  }
}

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

/mnt/B6F0D77CF0D74173/_Documents/Vladimir/YandexDisk/Arduino/_temp/test1/test1.ino:37:36: warning: taking address of temporary [-fpermissive]
       btn_1 = &(shButton){BTN_1_PIN};
                                    ^
/mnt/B6F0D77CF0D74173/_Documents/Vladimir/YandexDisk/Arduino/_temp/test1/test1.ino:46:36: warning: taking address of temporary [-fpermissive]
       btn_2 = &(shButton){BTN_2_PIN};
                                    ^

Как сделать правильно?

   btn_1 = new shButton(BTN_1_PIN);
    if (btn_1 != NULL)
    {
      buttons[0] = btn_1;
    }
1 лайк