Подтвердить что ты не робот

Пользовательский макрос assert С++

Я наткнулся на информативную статью: http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/ который указал на большое количество проблем, которые существуют в моем текущем пакете отладочных макросов.

Полный код окончательной версии макроса указан в конце статьи, если вы следуете ссылке.

Общая форма, представленная, выглядит так (кто-то, пожалуйста, поправьте меня, если я ошибаюсь в переносе):

#ifdef DEBUG
#define ASSERT(cond) \  
    do \  
    { \  
        if (!(cond)) \  
        { \  
            ReportFailure(#cond, __FILE__, __LINE__, 0); \
            HALT(); \
        } \  
    } while(0)  
#else  
#define ASSERT(cond) \  
    do { (void)sizeof(cond); } while(0) 

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

Один из них заключался в том, что вы не можете использовать этот макрос с тернарным оператором (т.е. cond?ASSERT(x):func()), и предложение заключалось в замене if() тройным оператором и некоторыми скобками, а также оператором запятой. Позже другой комментатор предоставил следующее:

#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif

Я думал, что использование логического и && является особенно умным в этом случае, и мне кажется, что эта версия более гибкая, чем одна, использующая if или даже тройную ?:. Еще лучше, что возвращаемое значение assert_handler может использоваться для определения того, должна ли программа останавливаться. Хотя я не уверен, почему это (HALT(), 1) вместо просто HALT().

Есть ли какие-то особые недостатки со второй версией здесь, которые я упустил? Это оборачивается do{ } while(0), завернутым вокруг макросов, но здесь это не нужно, потому что нам не нужно иметь дело с if s.

Как вы думаете?

4b9b3361

Ответ 1

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

int sum = (assert(a > 0), a) + (assert(b < 0), b);

который функционально совпадает с

assert(a > 0 && b < 0)
int sum = a + b;

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

Это немедленно означает, что если требуется собственный собственный макрос assert для имитации стандартного поведения и удобства использования assert, то использование if или do { } while (0) в определении assert не может быть и речи. Один из них ограничивается выражениями в этом случае, что означает использование оператора ?: или коротких замыкающих логических операторов.

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

Что касается (HALT(), 1)... Это делается так, потому что оператор && требует действительного аргумента. Возвращаемое значение HALT() может не представлять допустимый аргумент для &&. Это может быть void для того, что я знаю, а это означает, что просто HALT() просто не будет компилироваться в качестве аргумента &&. (HALT(), 1) всегда оценивается как 1 и имеет тип int, который всегда является допустимым аргументом для &&. Таким образом, (HALT(), 1) всегда является допустимым аргументом для &&, независимо от типа HALT().

Ваш последний комментарий о do{ } while(0), похоже, не имеет большого смысла. Точка включения макроса в do{ } while(0) заключается в рассмотрении внешних if s, а не if внутри определения макроса. Вам всегда приходится иметь дело с внешними if s, так как всегда есть шанс, что ваш макрос будет использоваться во внешнем if. В последнем определении do{ } while(0) не требуется, потому что этот макрос является выражением. И, будучи выражением, он уже, естественно, не имеет проблем с внешними if s. Поэтому нет ничего о них. Более того, как я сказал выше, включение его в do{ } while(0) полностью победит его цель, превратив его в не-выражение.

Ответ 2

Для полноты я опубликовал в С++ версию макроса assert-in 2 files:

#include <pempek_assert.h>

int main()
{
  float min = 0.0f;
  float max = 1.0f;
  float v = 2.0f;
  PEMPEK_ASSERT(v > min && v < max,
                "invalid value: %f, must be between %f and %f", v, min, max);

  return 0;
}

Вам будет предложено:

Assertion 'v > min && v < max' failed (DEBUG)
  in file e.cpp, line 8
  function: int main()
  with message: invalid value: 2.000000, must be between 0.000000 and 1.000000

Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:

Где

  • (I) gnore: игнорировать текущее утверждение
  • Игнорировать (F) orever: запомнить файл и строку, в которой было запущено утверждение и игнорировать его для оставшегося выполнения программы
  • Игнорировать (A) ll: игнорировать все остальные утверждения (все файлы и строки)
  • (D) ebug: перейдите в отладчик, если он подключен, иначе abort() (в Windows, система предложит пользователю подключить отладчик)
  • A (b) ort: вызов abort() немедленно

Вы можете узнать об этом больше:

Надеюсь, что это поможет.

Ответ 3

Хотя я не уверен, почему это (HALT(), 1) вместо просто HALT().

Я полагаю, что HALT может быть макросом (или другим именем в режиме ожидания) для exit. Скажем, мы хотели использовать exit(1) для нашей команды HALT. exit возвращает void, который не может быть оценен как второй аргумент &&. Если вы используете оператор запятой, который оценивает свой первый аргумент, то вычисляет и возвращает значение второго аргумента, мы имеем целое число (1), чтобы вернуться к &&, хотя мы никогда не достигаем этой точки, потому что HALT() будет заставит нас остановиться задолго до этого.

В принципе, любая функция, которая заполняется для HALT, вероятно, будет иметь возвращаемое значение void, потому что для нее не имеет смысла возвращать какое-либо значение. Мы могли бы заставить его вернуть int только ради макроса, но если мы уже взламываем макрос, немного больше хакеров не может повредить, не так ли?