Имитация механизма пространства имен средствами языка Си
Александр Бельченко
20 ноября 2004
Данная статья не претендует на истину в последней инстанции, или даже на практический совет. Это всего-навсего мои размышления о том, как использовать в Си некоторые удобные механизмы из других более высокоразвитых языков.
Кратко о механизме пространства имен
Во многих современных высокоуровневых языках программирования (C++/C#, Java, Python и проч.) имеется механизм пространства имен. Он позволяет разделять определения глобальных переменных, классов или просто выполняемых функций по непересекающимся «вселенным». Каждая такая «вселенная» содержит в себе свои определения, для доступа к которым необходимо использовать специальные методы доступа. Простейшим из методов доступа является использование префикса перед именем с названием этой «вселенной» т.е. названия этого пространства имен.
Например, в C++ используется синтаксис типа «name::var», в языке Python синтаксис типа «name.var».
В языке Си существует три жестко определенных пространства имен: глобальное для всей программы, локальное внутри модуля (статические переменные и статические функции) и локальное внутри конкретной функции. Механизм пространств имен, о котором я говорю в этой статье, позволяет разделить глобальное пространство на совокупность непересекающихся «вселенных».
К моему сожалению, язык Си за свою историю успел окостенеть настолько, что в нем уже не могут появиться столь значительные изменения, как поддержка механизма пространств имен. Пожалуй единственным прижившимся артефактом в некоторых современных реализациях языка Си для микроконтроллеров можно считать комментарии в стиле C++ две косые черты (//).
Зачем нужен механизм пространства имен?
Говоря простым языком он наиболее удобным для программиста способом позволяет разделить глобальные переменные и функции, объявленные в разных модулях или в разных пространствах имен.
Приведу упрощенный пример. В одном проекте я использую для работы с UART модуль, который я назвал uart.c. В нем есть функция для инициализации режимов UART пусть она будет называться init; функция для отправки байта send; функция для чтения принятого байта read. В другом проекте я использую модуль spi.c для работы с SPI-интерфейсом. Этот модуль также будет иметь функции для его инициализации, отправки байта и чтения байта.
В третьем проекте мне, например, будут нужны оба модуля для работы с обоими интерфейсами. Если я не приму мер, то имена функций init, send, read из этих двух модулей будут конфликтовать друг с другом. Если в распоряжении программиста имеется механизм пространства имен, то эта проблема решается достаточно просто с ее помощью. Если такого механизма нет, то приходится каждый раз выдумывать уникальные имена. Например, это могут быть InitUART, InitSPI либо uart_send, spi_read.
Имитация средствами языка Си
Механизм пространств имен предлагается имитировать следующим образом: для всех глобальных переменных и функций внутри одного модуля использовать префикс в имени, который совпадает с именем этого модуля.
Например, для модуля uart.c будет использоваться префикс «uart_» перед именами. Для модуля spi.c этот префикс будет уже «spi_». Таким образом для вышеприведенного примера имена функций в разных модулях будут иметь следующий вид: uart_init, uart_send, uart_read; spi_init, spi_send, spi_read.
При использовании такого нехитрого метода становится легко создавать уникальные имена для функционально похожих переменных, объектов и методов, объявленых в разных исходных файлах проекта. Использование в качестве префикса имени модуля, в котором определена данная переменная или функция, облегчает навигацию по большому проекту, содержащему множество исходников. Достаточно быстрого взгляда на имя переменной или функции, чтобы понять в каком исходнике искать ее определение.
Правило составления имени
Имя функции/переменной, содержащее в себе признак пространства имен, должно строиться по жесткой иерархической схеме, аналогичной схеме построения предложений в английском языке.
Для имен переменных эта схема будет выглядеть так:
Имя-модуля имя-переменной
Т.е. всегда первым членом составного имени переменной идет идентификатор (название) модуля, затем собственно имя переменной (существительное либо существительное+прилагательное).
Например, в модулях, которые реализуют обмен по некоторым интерфейсам, могут быть переменные:
uart_buffer_rx
uart_buffer_tx
uart_counter
spi_buffer_rx
spi_buffer_tx
spi_counter
Для имен функций эту схему можно выразить в терминах правил грамматики:
Подлежащее сказуемое дополнение
Либо эту схему можно выразить в виде иерархической взаимосвязи:
Субъект действие объект
Т.е. первым членом составного имени функции должен идти идентификатор (название) модуля, которому оно принадлежит. Вторым членом должен идти глагол, определяющий действие, производимое в функции. После глагола может следовать уточняющее глагол дополнение, которое помогает различать похожие функции.
Например, в модуле, который реализует работу с флэш-памятью, могут быть различные методы для записи и стирания содержимого памяти:
flash_write_byte
flash_write_float
flash_erase_full
flash_erase_sector
Недостатки
У описанного метода есть и свои недостатки. Поскольку это всего лишь имитация,
то этот метод работает в рамках модели языка Си, в котором не существует метода для ручного дробления глобального пространства имен программы. Поэтому в отличие от нативного механизма пространств имен (реализованного в других языках), для доступа к переменным внутри модуля приходится использовать те же самые глобальные длинные имена.