Вопрос про #define для отладки и вывод имени переменной

Sakolua
Offline
Зарегистрирован: 13.08.2019

Нашел такие вот полезные сокращения для дебага.

#define PRINT(x) Serial.print(x)

Но чаще надо вывести не просто значение, а еще и имя.

Serial.print("Name = "); Serial.println(Name);

Можно эти две команды как-то задефайнить похожим образом в одну? Name же одинаково.

#define PRINT(X) Serial.print("X = "); Serial.println(X);

??

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015
#define printIt(x) do { Serial.print(#x "=");Serial.println(x); } while (false)

Пользовать:

int N = 321; 
printIt(N);

 

sadman41
Offline
Зарегистрирован: 19.10.2016

А у меня вопрос: do { } while() какую-то специфическую задачу решают в этом выражении?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

А у меня вопрос: do { } while() какую-то специфическую задачу решают в этом выражении?

Простите, если Вы решили пошутить и вызвать ответную шутку, ... я только что встал, плохо соображаю и шутить не могу, могу только начать занудно объяснять для чего оно там. Или Вам это и нужно? Вы, правда не знаете?

Кстати, обратите внимание (!) я исправил там опечатку - убрал ";" в конце макроса - она там категорически не нужна, "пальцы сами как-то вставили".

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

никогда не понимал всех этих ваших макросов, и вот опять...

sadman41
Offline
Зарегистрирован: 19.10.2016

Нет, не шучу. Я просто оставил блок печати в { } и не заметил никаких проблем со встраиванием в код. Поэтому и уточняю - в каких случаях я получу геморрой.

asam
asam аватар
Offline
Зарегистрирован: 12.12.2018

sadman41 пишет:

Нет, не шучу. Я просто оставил блок печати в { } и не заметил никаких проблем со встраиванием в код. Поэтому и уточняю - в каких случаях я получу геморрой.

Согласен, фигурных скобочек должно быть достаточно.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

Я просто оставил блок печати в { } и не заметил никаких проблем со встраиванием в код. 

asam пишет:

фигурных скобочек должно быть достаточно.

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

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

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

int N = 321; 
if (N > 123) 
	printIt(N);
else
	printIt(N+1);

А если в макросе оставить только скобки (убрать do-while), то попробуйте её скомпилировать - фигвам.

Можно, конечно, написать вот так:

int N = 321; 
if (N > 123) 
	printIt(N)    // НЕТ точки с запятой!!!
else
	printIt(N+1);

но выглядит это неестественно, согласитесь.

Поэтому, решение с do-while стандартное и универсальное для функциональных макросов, его пишут автоматом, не задумываясь.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Спасибо, теперь буду знать. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Кстати, Sakolua,

возьмите вот эту библиотеку и поставьте её ко всем библиотекам. Сможете печатать просто функцией printf со всеми её наворотами, также сможете использовать потоки, типа ' Serial << "a=" << a << "\n"; '. Ну, и макрос, который я для Вас в первом посте написал, там тоже есть - printVar называется.

Sakolua
Offline
Зарегистрирован: 13.08.2019

а float в уно никак не поддержать?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

В смысле? Serial.print (и соответственно Serial <<) вполне его поддерживает. Или Вы о чём?

Sakolua
Offline
Зарегистрирован: 13.08.2019

float pi = 3.1459;

printf ("i = %d, pi = %f\n", i, pi);

"pi = ?"

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Опция компилятора есть спицальная. Для таких случаев

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Sakolua пишет:

а float в уно никак не поддержать?

Просто - нет. А сложно - оно Вам точно надо? :-)

Sakolua
Offline
Зарегистрирован: 13.08.2019

а почему бы нет? что в этом плохого? полно параметров в дробных, почти любой датчик

b707
Offline
Зарегистрирован: 26.05.2017

Sakolua пишет:

полно параметров в дробных, почти любой датчик

ошибаетесь, большинство (просто практически все) датчики работают в целых числах. А во флоат переводят только перед выводом - исключительно для тупых человеков.

Sakolua
Offline
Зарегистрирован: 13.08.2019

очень полезная информация, какая мне разница в чем они там работают, большинство вообще только сопротивление да напряжение меряют, так может мне температуру в омах считать? )))

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Sakolua пишет:

а почему бы нет? что в этом плохого? полно параметров в дробных, почти любой датчик

сейчас еще раз за сегодня объяснят, что работать надо с целыми

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Вполне вероятно, что для китайских даччиков так будет правильнее

Sakolua
Offline
Зарегистрирован: 13.08.2019

ua6em пишет:

Sakolua пишет:

а почему бы нет? что в этом плохого? полно параметров в дробных, почти любой датчик

сейчас еще раз за сегодня объяснят, что работать надо с целыми

мы тут про вывод говорили

sadman41
Offline
Зарегистрирован: 19.10.2016

Как бы это не было смешно, но BME680, например, концентрацию VOC в омах выдаёт.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Sakolua пишет:

очень полезная информация, какая мне разница в чем они там работают, большинство вообще только сопротивление да напряжение меряют, так может мне температуру в омах считать? )))

Считайте хоть в литрах, это Ваше дело, но посмотрите как работают с любым аналоговым датчиком на примере простейшего измерения напряжения на пине:

1. читаем напряжение с АЦП в целых "попугаях" - получаем целое число от 1 до 4 десятичных цифр.
2. переводим целые попугаи в число с плавающей точкой
3. для печати переводим число с плавающей точкой в целое и указание где поставить десятичную запятую
4. печатаем это целое, впихнув запятую куда надо.

Вы не видите здесь лишних шагов?

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

ЕвгенийП пишет:
Поэтому, решение с do-while стандартное и универсальное для функциональных макросов, его пишут автоматом, не задумываясь.
Возможно и стандартное, но не всегда срабатывает
Мне нужно вывести значения переменных, проверить условие и выполнить какие-то действия

 

#define printIt(x) do { Serial.print(#x "=");Serial.println(x); } while (false)
#define printIt2(x) ({ Serial.print(#x "=");Serial.println(x); })
void setup() {
  Serial.begin(115200);
  int N = 321; 
  printIt(N);
  int a = 1, b = 2, c = 3; 
  if (printIt2(a), printIt2(b), printIt2(c), c > a) {
    N--;
    printIt(N);
  }
}
void loop() {
}

Вывод

N=321
a=1
b=2
c=3
N=320

В выражении, когда определение используется как операнд, "стандартное" не собирается, а "нестандартное" работает

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Вполне возможно.

Мне правда непонятен приведённый пример (if (printIt2(a), printIt2(b), printIt2(c), c > a)) - зачем так навёрнуто и какой в этом смысл? Более осмысленного примера нет?

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Смысла столько же как и использование запятых в операторе for т.е. выполни одно, другое, третье и т.д.

Стивен Прата в учебнике говорит
Применение операции запятой не ограничивается только циклами for, однако,
именно здесь она чаще всего используется. Эта операция имеет еще два характерных
свойства.
Во-первых, она гарантирует, что выражения , которые она отделяет одно от
другого, вычисляются в порядке слева направо.
Во-вторых, значение всего выражения, содержащего операцию запятой, является
значением выражения вправой части. Результат оператора.
х = (у = З, (z = ++у + 2 ) + 5);

Выполняется последовательность действий, и только последнее используется для присваивания.
В операторе if точно так же, выполняется последовательно до последнего разделителя, после него - выражение для проверки.  

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Спасибо, буду знать :-)))

Sakolua
Offline
Зарегистрирован: 13.08.2019

ЕвгенийП пишет:

Sakolua пишет:

а float в уно никак не поддержать?

Просто - нет. А сложно - оно Вам точно надо? :-)


ну простое serial.print печатает pi

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Sakolua пишет:
ну простое serial.print печатает pi
Вы, наконец, заметили мой пост #11? :-)))

baby_in_Arduino
Offline
Зарегистрирован: 21.07.2019

я даже не предположу где вообще такое нахрен нужно
(if (printIt2(a), printIt2(b), printIt2(c), c > a))
кроме как чемпионата по браинфак)

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

baby_in_Arduino пишет:
я даже не предположу где вообще такое нахрен нужно
А не было цели сделать что-то нужное. Цель была возразить и самоутвердиться. На просьбу же привести более осмысленный пример, последовало долгое словоблудие.

Sakolua
Offline
Зарегистрирован: 13.08.2019

"заметили мой пост"

так вот и не понятно - почему тут работает, а тут уже нет?

b707
Offline
Зарегистрирован: 26.05.2017

Sakolua пишет:

почему тут работает, а тут уже нет?

а в жизни всегда так - на жигули хватает, а на майбах уже нет... И почемутак??

Sakolua
Offline
Зарегистрирован: 13.08.2019

философ флудерный

Green
Offline
Зарегистрирован: 01.10.2015

Алексей. пишет:

ЕвгенийП пишет:
Поэтому, решение с do-while стандартное и универсальное для функциональных макросов, его пишут автоматом, не задумываясь.
Возможно и стандартное, но не всегда срабатывает
Мне нужно вывести значения переменных, проверить условие и выполнить какие-то действия

 

#define printIt(x) do { Serial.print(#x "=");Serial.println(x); } while (false)
#define printIt2(x) ({ Serial.print(#x "=");Serial.println(x); })
void setup() {
  Serial.begin(115200);
  int N = 321; 
  printIt(N);
  int a = 1, b = 2, c = 3; 
  if (printIt2(a), printIt2(b), printIt2(c), c > a) {
    N--;
    printIt(N);
  }
}
void loop() {
}

Вывод

N=321
a=1
b=2
c=3
N=320

В выражении, когда определение используется как операнд, "стандартное" не собирается, а "нестандартное" работает

С одними фигурными как то не красиво. Я тоже раньше писал  через do while (0), но затем обленился и пишу через запятую в обычных скобках.)

#define printIt2(x) (Serial.print(#x "="), Serial.println(x))

 

Green
Offline
Зарегистрирован: 01.10.2015

Что то специалисты все молчат... А хотелось бы критики.)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Зачем?

Green
Offline
Зарегистрирован: 01.10.2015

Ну как зачем? Должны же быть подводные камни, которые мне совсем не видны?)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Green,

ладно, только букв будем много. Мой талант - один у папы с мамой, нет у него сестёр :-(

Понимаете, не бывает «лучших для любой ситуации» решений. В данном топике я «на автомате» использовал do-while (безо всякой мысли вбросить тему для дискуссии, поверьте). Этот подход для данного применения вполне хорош. Меня спросили, а чем может (в какой-то ситуации!!!) подвести просто пара фигурных скобок. Я ответил примером. Поверьте, если бы меня спросили «а чем может (в какой-то другой ситуации!!!) подвести do-while», я бы тоже ответил, не сомневайтесь. Не бывает решений на все случаи жизни. Это как спросить «чем вкуснее закусывать – салом или шоколадом?». Пока мы не знаем, что именно нужно закусывать, водку или коньяк – ответа быть не может.

Если Вас интересует конкретно пара круглых скобок, ну давайте на неё посмотрим и поищем ей «место под солнцем», где она заведомо лучше того же do-while, а где хуже.

Первое, что бросается в глаза – выражение в круглых скобках может иметь значение (в каких-то случаях), а do-while – никогда, его нельзя ничему присваивать. Отсюда вывод: если наш «функциональный макрос» должен возвращать значение, которое можно чему-то присвоить, то про do-while нужно просто забыть, т.к. его нельзя ничему присвоить, а скобки могут и подойти!

А как в случае, если не нужно возвращать значение? Если бы делаем «void макрос»? Что использовать? do-while или скобки? Всё равно? Ан нет, в этом случае do-while предпочтительнее! Сейчас покажу на примере.

Допустим, нам нужно атомарно брать значение волатильного флага в локальную переменную, сбрасывая при этом значение волатильного флага (довольно типичная задача). Я написал два макроса – один с do-while, а другой со скобками (не смотрите на варварскую работу с SREG – это искусственный пример для демонстрации проблемы).

#include <Printing.h>

//#define WITH_DO_WHILE

#ifdef WITH_DO_WHILE
	#define getFlag(volatileFlag, localFlag) \
		do {	\
			SREG &= ~bit(SREG_I),	\
			localFlag = volatileFlag;	\
			volatileFlag = false;	\
			SREG |= bit(SREG_I);	\
		} while (false)
#else
	#define getFlag(volatileFlag, localFlag) \
		(	\
			SREG &= ~bit(SREG_I),	\
			localFlag = volatileFlag,	\
			volatileFlag = false,	\
			SREG |= bit(SREG_I)	\
		)
#endif

void setup(void) {
	bool volatile myFlag;
	bool flag;
	Serial.begin(115200);

	myFlag = true;
	getFlag(myFlag, flag);
	Serial.println("=== With true");
	printVar(myFlag);
	printVar(flag);
//
	getFlag(myFlag, flag);
	Serial.println("=== With false");
	printVar(myFlag);
	printVar(flag);
}

void loop(void) {}

Запускаем и, независимо от того какой макрос используется (закомментирована строка №4 или нет), получаем один и тот же результат

=== With true
myFlag=0
flag=1
=== With false
myFlag=0
flag=0

Т.е. flag получил значение, которое было у myFlag до вызова макроса, а myFlag сбросился. Всё здорово! Но это только пока мы делаем всё правильно.

А вот теперь, представьте, что другой человек модифицирует этот код (или тот же человек, но через год, или там с бодуна) и по имени макроса getFlag этот программист предположит (ошибочно), что макрос не просто меняет переменные, а заодно и возвращает новое значение flag. Сам-то макрос спрятан в далёких .h файлах, его не видно, и вот человек так ошибся. Этот программист напишет (может написать) что-то вроде вот такого:

#include <util/atomic.h>
#include <Printing.h>

//#define WITH_DO_WHILE

#ifdef WITH_DO_WHILE
	#define getFlag(volatileFlag, localFlag) \
		do {	\
			SREG &= ~bit(SREG_I),	\
			localFlag = volatileFlag;	\
			volatileFlag = false;	\
			SREG |= bit(SREG_I);	\
		} while (false)
#else
	#define getFlag(volatileFlag, localFlag) \
		(	\
			SREG &= ~bit(SREG_I),	\
			localFlag = volatileFlag,	\
			volatileFlag = false,	\
			SREG |= bit(SREG_I)	\
		)
#endif

void setup(void) {
	bool volatile myFlag;
	bool flag;
	Serial.begin(115200);

	myFlag = true;
	flag = getFlag(myFlag, flag);
	Serial.println("=== With true");
	printVar(myFlag);
	printVar(flag);
//
	flag = getFlag(myFlag, flag);
	Serial.println("=== With false");
	printVar(myFlag);
	printVar(flag);
}

void loop(void) {}

И что получит? С вариантом через do-while (строка №4 НЕ закомментирована) он получит ошибку компиляции! А вот с вариантом через скобки он получит ворнинг (которых полно в «штатных» библиотеках, и на которые новички просто не обращают внимания), но всё нормально скомпилируется! Только работать будет вот так:

=== With true
myFlag=0
flag=1
=== With false
myFlag=0
flag=1

Т.е. локальный flag всегда истина, независимо от того, какое было значение у волатильного myFlag до вызова макроса. И ищи теперь этот баг, пока не посинеешь!

Т.е., давайте резюмируем: когда при написании «по жизни void» макроса мы использовали «по жизни void» конструкцию do-while, она нас выручила от ошибки в исполняемом коде, выдав ошибку при компиляции. А когда мы для «void макроса» использовали возвращающую значение конструкцию «круглые скобки», ошибки компиляции не случилось, зато ошибка переползла в исполняемый код, где искать её намного сложнее.

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

Вот как-то так.

Green
Offline
Зарегистрирован: 01.10.2015

Ого, Евгений! Прям опера, не ожидал). Я всё понял. Да, действительно, нюансик есть. И, как бы, для себя сойдёт, а вот для "широкого применения" пожалуй не очень. Поэтому видать все и используют классику do-while.
ОК. Благодарю за труды. Как нибудь на досуге поищу Ваше сравнение.)