static и inline функции вместо макросов

      Комментарии к записи static и inline функции вместо макросов отключены

Помечено: , , ,

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 1 месяц, 1 неделя назад.

  • Автор
    Сообщения
  • #3963

    Автор статьи — МУХ. Обратите внимание, что статья в большей мере относится к Си, чем к С++. В С++, безусловно, макросы тоже использовать не следует, но и «вычисление констант во время компиляции» не является оправданием для их использования. В С++ есть замечательный спецификатор constexpr (про него в статье речь идти не будет).

    «приходят в овечьей одежде, а внутри суть волки хищные.»
    (Мф 7:15)

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

    #define cube(x) ((x) * (x) * (x))

    А вот так его вызывает программист в своей программе:

    int a = 2;
    int b = cube(++a);

    Препроцессор заменяет все x в выражении на ++a и в итоге компилятор получает на вход уже строку:
    int b = ++a * ++a * ++a

    Как думаете, совподает ли это с ожиданиями программиста?

    С приходом стандарта C99 стало возможно использование inline функций вместо макросов. И этим нужно пользоваться.

    Решение проблемы:

    inline int cube(int x) { 
        return x * x * x;
    }
    
    int a = 2;
    int b = cube(a);

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

    Пример №2

    Макрос в следующем примере предназначен для подсчета количества вызовов переданной функции:

    unsigned count = 0;
    #define exec_bump(func) (func(), ++count)

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

    void myfunc() { 
        printf("Функция myfunc() вызвана в %d-й раз\n", count);
    }

    Вроде все хорошо. Но теперь представьте, что в функции, из которой вызывается макрос exec_bump, есть локальная переменная с именем count:

    void aFunc(void) {
        unsigned count = 0;
        while (count ++ < 10) {
            exec_bump(myfunc);
        }
    }

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

    void aFunc(void) {
        unsigned count = 0;
        while (count ++ < 10) {
            myfunc(), ++count;
        }
    }

    Инкрементироваться будет естественно локальная переменная, а глобальная так и останется равной нулю. В цикле будет несколько раз выведено сообщение: Функция myfunc() вызвана в 0-й раз.

    В этом примере нарушено еще одно правило, которое мы когда нибудь рассмотрим подробнее, не использовать повторно имена в локальных пространствах имен.

    Правильное решение

    unsigned count = 0;
     
    void myfunc() {
        printf("Функция myfunc() вызвана в %d-й раз\n", count);
    }
    
    typedef void *exec_func(void);
    inline void exec_bump(exec_func f) { 
        f();
        ++count;
    }
    
    void aFunc(void) {
        unsigned count = 0;
        while (count++ < 10) { 
            exec_bump(myfunc);
        }
    }

    Теперь связывание идентификатора count в функции exec_bump производится на стадии компиляции для области видимости функции exec_bump. А в этой области видимости есть только одна переменная с именем count глобальная. Переменную count из области видимости функции aFunc тут не видно. Аналогично, как в первом примере не было видно ее из функции myfunc.

    Что делать, если нет поддержки C99

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

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

    Исключения

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

    2. Макросы могут использоваться для конкатенации токенов или преобразования фрагмента кода в статическую строку.

    3. Для вычисления констант во время компиляции. Например, использование макроса
    #define ADD_M(a, b) ((a) + (b))
    с конкретными числовыми значениями, будет заменено на числовой результат во время компиляции.

    4. Для создания типо-независимых функций, аналогичных шаблонам в С++.

Для ответа в этой теме необходимо авторизоваться.