Имитация механизма пространства имен средствами языка Си

Александр Бельченко
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

Недостатки

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