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

Объяснение С++ FAQ небезопасный макрос?

В соответствии с часто задаваемыми вопросами С++ макросы являются злыми:

[9.5] Почему я должен использовать встроенные функции вместо обычного старого #define макросы?

Потому что #define макросы являются злыми по-разному: зло # 1, зло # 2, зло 3 и зло 4. Иногда вы должны использовать их в любом случае, но они все еще злой.      В отличие от макросов #define встроенные функции избегают печально известных макрокоманд, поскольку встроенные функции всегда точно оценивают каждый аргумент один раз. Другими словами, вызов встроенной функции выполняется семантически например, вызывая регулярную функцию, только быстрее:

// A macro that returns the absolute value of i
#define unsafe(i)  \
        ( (i) >= 0 ? (i) : -(i) )

// An inline function that returns the absolute value of i
inline
int safe(int i)
{
  return i >= 0 ? i : -i;
}

int f();

void userCode(int x)
{
  int ans;

  ans = unsafe(x++);   // Error! x is incremented twice
  ans = unsafe(f());   // Danger! f() is called twice

  ans = safe(x++);     // Correct! x is incremented once
  ans = safe(f());     // Correct! f() is called once
}

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

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

Может кто-нибудь объяснить, почему unsafe(x++) увеличивает x дважды? Я не могу понять.

4b9b3361

Ответ 1

Запуск его через препроцессор показывает проблему. Используя gcc -E (также можно использовать cpp -P, где опция -P также подавляет сгенерированные строки #),

inline
int safe(int i)
{
  return i >= 0 ? i : -i;
}

int f();

void userCode(int x)
{
  int ans;

  //    increment 1      increment 2 (one of these)
  //        |             |     |
  //        V             V     V
  ans = ( (x++) >= 0 ? (x++) : -(x++) );
  ans = ( (f()) >= 0 ? (f()) : -(f()) );

  ans = safe(x++);
  ans = safe(f());
}

В качестве безупречных шумовых заметок функция f() также дважды вызывается макросом unsafe. Возможно, он чист (не имеет побочных эффектов), поэтому он не ошибается, как таковой. Но все же субоптимальный.

Итак, поскольку встроенные функции, как правило, более безопасны, чем макросы, подобные функциям, поскольку они работают на одном и том же семантическом уровне с другими основными элементами: переменными и выражениями; и для манифестных констант, enum часто может быть более аккуратным; Каковы преимущества использования макросов?

Установленные константы, известные только во время компиляции. Вы можете определить макрос из командной строки при компиляции. Вместо

#define X 12

в исходном файле, вы можете добавить

-DX=12

в команду cc. Вы также можете #undef X из командной строки с помощью -UX.

Это позволяет такие вещи, как условная компиляция, например.

#if X
   do this;
#else
   do that;
#endif
   while (loop);

для управления make файлом, который, возможно, сгенерирован с помощью configure script.

X-макросы. Наиболее убедительное использование для X-Macros, IMO, связывает идентификаторы enum с печатными строками. Хотя сначала это выглядит смешно, это уменьшает проблемы дублирования и синхронизации с такими параллельными определениями.

#define NAMES(_) _(Alice) _(Bob) _(Caravaggio) _(DuncanIdaho)
#define BARE(_) _ ,
#define STRG(_) #_ ,
enum { NAMES(BARE) };
char *names[] = { NAMES(STRG) };

Обратите внимание, что вы можете передать имя макроса в качестве аргумента другому макросу, а затем вызвать переданный макрос, используя аргумент, как если бы он сам был макросом (потому что он один). Подробнее о X-Macros см. этот вопрос.

Ответ 2

Макросы эффективно копируют/вставляют перед компиляцией программы.

unsafe(x++)

Стал бы

( (x++) >= 0 ? (x++) : -(x++) )

Ответ 3

Препроцессор заменяет макрос перед компиляцией.

Компилятор видит это:

  ( (x++) >= 0 ? (x++) : -(x++) )

Ответ 4

unsafe(x) дважды оценивает выражение x. Однажды, чтобы определить его значение истинности, а затем второй раз в одной из двух ветвей тернарного оператора. Встроенная функция safe получает оцененный аргумент: выражение оценивается один раз перед вызовом функции, а вызов функции работает с локальными переменными.

unsafe на самом деле не так опасен, как может быть. Тернарный оператор вводит точку последовательности между оценкой теста и оценивает либо последующее, либо альтернативное выражение. unsafe(x++) будет надежно увеличивать x дважды, хотя, конечно, проблема в том, что это поведение неожиданно. В общем, макросы, которые расширяют выражение более одного раза, не имеют такой уверенности. Обычно они создают прямое поведение undefined!

Около 1999 года я выпустил модуль модуля библиотеки для ловушки использования макросов с побочными эффектами.

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

Вот тестовая программа unsafe.c. Обратите внимание, что он включает заголовочный файл sfx.h и использует макрос SFX_CHECK в последовательности токенов расширения unsafe:

#include "sfx.h"

#define unsafe(i)  \
          ( (SFX_CHECK(i)) >= 0 ? (i) : -(i) )

inline
int safe(int i)
{
  return i >= 0 ? i : -i;
}

int f(void)
{
  return 0;
}

int main(void)
{
  int ans;
  int x = 0;

  ans = unsafe(x++);   // Error! x is incremented twice
  ans = unsafe(f());   // Danger! f() is called twice

  ans = safe(x++);     // Correct! x is incremented once
  ans = safe(f());     // Correct! f() is called once
}

Скомпилируем все и запустим из командной строки Linux:

$ gcc unsafe.c hash.c except.c sfx.c -o unsafe
$ ./unsafe
unsafe.c:22: expression "x++" has side effects
unsafe.c:23: expression "f()" may have side effects

Обратите внимание, что x++ имеет побочные эффекты, а f может или не может. Таким образом, сообщения по-разному сформулированы. Функция, вызываемая дважды, не обязательно является проблемой, потому что функция может быть чистой (не имеет побочных эффектов).

Вы можете получить здесь здесь, если вам интересно, как это работает. Если отладка включена; конечно SFX_CHECK можно отключить, чтобы он ничего не делал (аналогично assert).

В первый раз, когда оценивается защищенное выражение SFX_CHECK, оно анализируется, чтобы определить, могут ли быть побочные эффекты. Поскольку этот синтаксический анализ выполняется без доступа к информации таблицы символов (как объявляются идентификаторы), это неоднозначно. Парсер преследует несколько стратегий разбора. Откат выполняется с использованием библиотеки обработки исключений на основе setjmp/longjmp. Результаты синтаксического анализа хранятся в хеш-таблице с ключевым словом для текстового выражения для более быстрого поиска в будущих оценках.