libc6: оператор запятой в определении макроса assert - PullRequest
3 голосов
/ 26 мая 2019

Моя система использует libc6 2.29. В /usr/include/assert.h я могу найти определение assert() macro:

/* The first occurrence of EXPR is not evaluated due to the sizeof,
   but will trigger any pedantic warnings masked by the __extension__
   for the second occurrence.  The ternary operator is required to
   support function pointers and bit fields in this context, and to
   suppress the evaluation of variable length arrays.  */
#  define assert(expr)                          \
  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({         \
      if (expr)                             \
        ; /* empty */                           \
      else                              \
        __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);   \
    }))

Интересно, зачем использовать оператор запятой и что означает «The first occurrence of EXPR is not evaluated due to the sizeof».

Какая проблема возникнет при использовании следующего определения:

#  define assert(expr)                      \
  ({                                        \
      if (expr)                             \
           ; /* empty */                            \
      else                              \
           __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);    \
    })

Edit:

какое значение получает оператор ({}), если expr истинно?

Можно ли переписать определение assert () следующим образом?

#  define assert(expr)                          \
  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({         \
      if (!expr)                                \
          __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \
    }))

Где проблемы с этим последним определением?

Ответы [ 2 ]

4 голосов
/ 26 мая 2019

Я не уверен на 100% в этом, но я попробую.

Сначала давайте рассмотрим несколько вещей, используемых здесь.

  • Оператор запятой отбрасывает первые результаты выражения n-1 и возвращает n-й результат.Он часто используется как точка последовательности , поскольку гарантируется, что выражения будут оцениваться по порядку.

  • Использование здесь __extension__, что является GNUМакрос LibC, используется для маскировки любых предупреждений о специфичных для GNU расширениях в заголовках в средах компиляции, которые задают педантичные предупреждения, через -ansi или -pedantic и т. Д. Обычно при таких компиляторах использование специфичного для компилятора расширения приводит кпредупреждение (или ошибка, если вы работаете в -Werror, что довольно распространено), но, поскольку в тех случаях, когда используются библиотеки и компиляторы GNU, libc позволяет себе использовать некоторые расширения, где это можно безопасно сделать.

Теперь, поскольку в реальной логике утверждения может использоваться расширение GNU (как указано при использовании __extension__), любые реальные предупреждения, которые могло бы выдвинуть само выражение, учитывая его семантику (то естьвыражение, переданное в assert(expr)) будет замаскировано, так как это выражение будет семантически расположено в блоке __extension__и, таким образом, маскируется.

Следовательно, должен быть способ, позволяющий компилятору показывать эти предупреждения, но без оценки фактического выражения (поскольку выражение может иметь побочные эффекты, а двойная оценка может вызватьнежелательное поведение).

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

Например, если у нас есть функция int blow_up_the_world(), то выражение sizeof(blow_up_the_world()) найдет размер результата выражения (в данном случае int) без фактической оценки выражения.Использование sizeof() в этом случае означало, что мир фактически не был бы взорван.

Однако, если expr, переданный assert(expr), содержал код, который в противном случае вызвал бы предупреждение компилятора (например, используярасширение в режимах -pedantic или -ansi), компилятор все равно будет отображать эти предупреждения, даже если код находится внутри sizeof() - предупреждений, которые в противном случае были бы замаскированы внутри блока __extension__.

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

Наконец, они хотели всего этого, но до фактической оценки и хотели сохранить assert() каквыражение, поэтому вместо использования do{}while() блока или чего-то подобного, что в конечном итоге приведет к тому, что assert() будет оператором, вместо этого они использовали оператор запятой, чтобы отбросить результат первого sizeof() трюка.

3 голосов
/ 26 мая 2019
  1. ({ не является стандартным C и будет вызывать предупреждения или ошибки в стандартных режимах компиляции C.
  2. Поэтому они используют __extension__, что отключит любую такую ​​диагностику.
  3. Однако __extension__ также замаскирует нестандартные конструкции в expr, которые вы хотите диагностировать.
  4. Именно поэтому им нужно expr повторить дважды, один раз внутри __extension__ и один раз снаружи.
  5. Однако expr нужно оценивать только один раз.
  6. Так что онизапретить другую оценку, поместив другое вхождение expr в sizeof.
  7. Просто sizeof(expr) недостаточно, потому что он не будет работать для таких вещей, как имена функций.
  8. Так что вместо этого используется sizeof((expr) ? 1 : 0), у которого нет этой проблемы.
  9. Таким образом, две части сгенерированного выражения: (а) sizeof((expr) ? 1 : 0) и (б) часть __extension__(...).
  10. Первая часть необходима только для диагностики, если что-то не такс expr.
  11. Вторая часть выполняет фактическое утверждение.
  12. Наконец, две части связаны с запятой.
...