Пользовательская реализация assert(): не выводит сообщение об ошибке

Я пишу свой собственный макрос assert() на Arduino, как показано ниже.
Когда я запускаю его из main (), он всегда работает правильно (печатает сообщение и попадает в бесконечный цикл), однако, когда я вызываю его в функции класса, он застревает, ничего не печатая. Почему это так?

assert.cpp/.h

#ifndef _ASSERT_H_
#define _ASSERT_H_

void assertion_failure(const char* expr, const char* file, int linenum);

#define assert(expr) \
    if (expr) ; \
    else assertion_failure(#expr, __FILE__, __LINE__)


#endif /* _ASSERT_H_ */
#include "assert.h" 
#include "Arduino.h"

void assertion_failure(const char* expr, const char* file, int linenum)
{
    Serial.print("Assertion failed in '");
    Serial.print(file);
    Serial.print("' on line ");
    Serial.print(linenum);
    Serial.print(": ");
    Serial.println(expr);
    Serial.flush();

    while (1);
}

И контекст, в котором assert застревает ниже (однако он хорошо работает, когда я вызываю его в main ()):

int ADS1256::assert_debug()
{
    uint8_t id = read_register(STATUS) >> 4;
    assert(false);    // Застревает здесь, ничего не печатая на экран
    return id;
}

Редактировать: похоже, это как-то связано с конфигурацией сборки. Я использую систему сборки PlatformIO. Это не работает, когда я использую эту файловую структуру:

├── include
│   └── assert.h
├── lib
│   ├── ADS1256
│      ├── ads1256.cpp
│      ├── ads1256.h
│      └── ads1256_regmap.h
├── src
    ├── assert.cpp
    └── main.cpp

Однако, если я перемещу assert.cpp/.h в выделенный каталог в lib/, он будет работать нормально. В чем причина этого? Как я могу выяснить, почему это произошло?

Он работает с этим макетом (опять же, используя конфигурацию сборки PlatformIO по умолчанию):

├── include
├── lib
│   ├── ADS1256/
│   ├── Assert/
│       ├── assert.cpp
│       └── assert.h
├── src
    └── main.cpp

, 👍1

Обсуждение

Ваш источник называется “Assert.c” или “Assert.cpp? Вы действительно пишете main (), а не setup() и " loop ()"? Если да, то вызываете ли вы init() в начале main()?, @Edgar Bonet

Я попробовал код, который вы опубликовали, с минимальной заглушкой для класса ADS1256: он работает так, как ожидалось. Таким образом, ваша проблема заключается в какой-то части вашего кода, которую вы не показываете., @Edgar Bonet

Откуда вызывается assert_debug ()?, @Majenko

@EdgarBonet Он назван assert.cpp (моя ошибка при написании поста, извините). Я действительно вызываю main (), а не setup+loop., @Jurc192

В этом процессе вы изменили свою директиву #include?, @timemage

@timemage нет, я всегда использую одно и то же: #include "assert.h". Я предполагаю, что он каким-то образом конфликтует с макросом assert (), определенным где-то в цепочке инструментов avr-gcc. Но как я могу это проверить/доказать?, @Jurc192

Это в основном то, что я сейчас пишу. Она не закончена, но я думаю, что просто опубликую ее. Я могу попробовать обновить информацию., @timemage


1 ответ


Лучший ответ:

0

Согласно комментарию к вопросу, это не так закончено, как хотелось бы, но это начало.

Я глубоко подозреваю,что вы столкнулись с конфликтом между именем вашего заголовка и стандартным заголовком assert.h. Вы используете то же имя заголовка и те же имена макросов, что и в стандартном заголовке c, который является частью как c++, так и "Arduino". Стандартный assert() с тем, как все обычно настраивается на ядре AVR Arduino, в основном просто входит в плотный цикл и ничего не делать.

Я бы подумал о том, чтобы назвать ваш макрос чем-то иным, чем assert, и назвать ваш заголовок чем-то иным, чем assert.h. Если вы хотите получить точные сведения о том, почему, нам нужно увидеть такие вещи, как ваш порядок пути включения и сами директивы включения. Даже тогда совет будет примерно таким же.

Диагностика

--save-temps

В platform.io вы должны иметь возможность добавить --save-temps в командную строку avr-gcc, чтобы заставить ее производить (или сохранять) вывод этапа предварительной обработки. GCC комментирует их, показывая, откуда взялся каждый раздел всего вывода, так что вы можете видеть, какие заголовки фактически включала каждая директива #include и в каком порядке все это происходило.

НДЕБУГ

Что-то еще, что вы можете попробовать в качестве диагностической меры, - это определить NDEBUG в настройках вашего проекта. Обычно это определяется IDE для "режима выпуска". Это в основном превращает assert() в NOP, что, в свою очередь, означает, что он не будет делать обычную блокировку avr-libc. Если вы видите, что утверждение avr-libc расширяется вместо вашего собственного, то поведение изменится не только на блокировку, но и на то, что будет делать ваше собственное утверждение.

Поддержка Arduino assert() на основе AVR

assert() avr-libc пытается напечатать свое сообщение об утверждении на stderr, который не настроен на ардуино на основе AVR, чтобы что-то делать. Таким образом, внешнее поведение заключается в том, что он запирается. Тем не менее, вы можете завершить реализацию avr-libc stderr с точки зрения функции Serial.write Arduino, и в этом случае обычное утверждение avr-libc будет работать просто отлично. Вот один из примеров завершения печати stdio avr-libc в терминах Serial.

__FILE__ Предостережение и SRAM

То, о чем вы должны знать при использовании __FILE__и поэтому утверждать (будь то avr-libc или ваш), заключается в том, что __FILE__ расширяется до строкового литерала, который является полным путем к исходному имени файла, которое может быть довольно длинным. На UNO (вы пометили) они идут в SRAM. Таким образом, если вы используете assert в паре разных файлов, вы можете очень быстро съесть доступный SRAM на AVR.

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

,

Спасибо, что указали мне на реализацию avr-libc sterr/assert! Таким образом, мы согласны на столкновение со стандартным заголовком assert.h. Но я хочу понять, в чем и как возникает проблема. Это имя файла, это имя макроса, и то, и другое? Как я могу проверить, какая реализация была использована (например, проверка двоичных файлов, символов или sth)? Я попытался использовать флаг препроцессора -H (показывает абсолютные пути включенных заголовков), но не увидел в нем ничего полезного..., @Jurc192

Э-э-э, я постараюсь сделать обновление, чтобы решить эту проблему., @timemage

См.раздел Диагностика и --save-temps. Я не собираюсь (оставил это раньше) идти намного дальше с этим ответом, не выяснив точно, что попросить вас сделать или включить в вопрос, а затем попросить вас предоставить это, что, вероятно, добавит много шума к вопросу., @timemage

Я задал тот же вопрос в PlatformIO [community](https://community.platformio.org/t/custom-assert-cpp-h-clashing-with-avr-gccs-assert/21865/3) и столкновение с макросом assert avr-libc действительно является проблемой., @Jurc192

Если это действительно тот ответ, который вы ищете, то вы можете также опубликовать свой собственный ответ, вызывающий этот ответ, и я избавлюсь от этого. Вам нужно будет пометить свой как принятый., @timemage