Макросы vs функции в Си

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

Когда программист только знакомится с макросами, они могут показаться ему самыми, что ни на есть обычными вызовами функций. Да, с немного странным синтаксисом, но все-таки функциями, да и ведут они себя, как функции. Так в чем же разница?

Если говорить условно, то макрос — это функция обработки и замены программного кода. После того, как будет выполнена сборка программы, макросы меняются на макроопределения. Как это выглядит, лучше рассмотреть на примере:

При этом данный код преобразуется в другой (отработавший код опустим):

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

Однако если попытаться выполнить тот же трюк с помощью макроса, то в процессе компиляции будет выброшена ошибка. Дело в том, что обе переменные по итогу станут находиться в одной функции:

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

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

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

Однако в C99 и C++ есть альтернатива макросам — встраиваемые (inline) функции. Если добавить перед функцией ключевое слово inline, компилятор получит указание включить тело функции непосредственно в место вызова функции (как и в случае с макросом). Причем встраиваемые функции могут быть отлажены, плюс у них существует проверка типов.

Но ключевое слово inline — это лишь подсказка для компилятора, то есть это не строгое правило, в результате чего компилятор может и проигнорировать данную подсказку. Дабы так не случилось, в gcc существует атрибут always_inline, заставляющий компилятор встраивать функцию. Конечно, встраиваемые функции — вещь неплохая, поэтому может возникнуть мысль, что применение макросов становится нецелесообразным. Но на деле это не совсем так. Однако о том, как лучше использовать макросы в Си, мы поговорим в следующий раз, поэтому следите за новостями!

По материалам статьи «How to properly use macros in C»