Глобальное состояние модуля

Последнее обновление: 16.09.2023

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

Таким образом, доступ к этому состоянию ограничивается текущим модулем. А функции, которые выполняют операции с этим состоянием, будет определять API модуля и объявляются в заголовочном файле. Благодаря этому внешний код посредством функций может выполнять действия над состоянием модуля. А модуль внутри функции может проконтролировать эти действия. Формально это выглядело бы следующим образом:

/* API (заголовочный файл) */
// условные операции с состоянием
int addNext(int value);


/* Реализация в модуле */
// условное состояние
static int sum = 0;

// реализация API
int addNext(int value)
{
  sum = sum + value;
  return sum;
}

/* Внешний код, который обращается к API модуля */
int result;
result = addNext(10);
result = addNext(20);

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

Рассмотрим небольшой пример. Сначала определим API. Для этого создадим следующий файл database.h:

// вывод одного объекта по индексу
extern void printUser(int); 
// вывод всех объектов
extern void printUsers();

Здесь определены объявления функций printUser и printUsers для вывода одного объекта по индексу и всех объектов из условной базы данных соответственно.

Далее определим реализацию этих функций в файле database.c:

#include <stdio.h>
#include "database.h" 
 
#define DATA_MAX_SIZE 5
// условные данные
static char* data[DATA_MAX_SIZE] = {"Tom", "Bob", "Sam", "Alice", "Kate"};
 
void printUser(int index)
{
    if(index > -1 && index < DATA_MAX_SIZE)
        printf("%s\n",data[index]);
    else
        printf("User not found\n");
}

void printUsers()
{
    printf("Users List\n");
    for(size_t i =0; i < DATA_MAX_SIZE; i++)
    {   
        printf("%s\n",data[i]);
    }
}

В данном файле/модуле определено общее, разделяемое состояние в виде массива указателей char*, то есть строк. Это состояние не зависит от внешнего кода и скрыто от него применением оператора static. То есть массив data доступен только в текущем модуле. А внешний код может обращаться к нему только опосредственно - через функции.

Для тестирования определим файл app.c со следующим кодом:

#include <stdio.h>
#include "database.h"

int main(void)
{
    printUser(1);
    printUsers();
}

Для файла "app.c" состояние модуля database.c - массив data доступно только через функции API модуля, а напрямую недоступно.

Сокмпилируем и запустим программу:

c:\C>gcc -Wall -pedantic app.c database.c -o app & app
Bob

Users List
Tom
Bob
Sam
Alice
Kate

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850