Возвращение набора данных фиксированной длины

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

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

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

#define DATA_SIZE 256   // фиксированное количество данных
// некоторые данные
static char data[DATA_SIZE]
// функция, которая получает от приемника буфер и копирует в него данные
void getData(char* buffer)
{
  memcpy(buffer, data, DATA_SIZE);
}

Код компонента-приемника данных:

// буфер, который передается источнику и в который копируются данные
static char buffer[DATA_SIZE]
// передаем буфер для копирования в него
getData(buffer);

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

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

Рассмотрим небольшой пример. Пусть у нас есть заголовочный файл user.h, где определена структура User, которая представляет данные:

struct User{
  char* name;
  unsigned age;
};

#define DATA_COUNT 5 // размер данных

Кроме того, здесь определена константы DATA_COUNT, которая будет представлять количество структур в наборе данных.

Пусть также определен файл user.c, который определяет источник данных:

#include <stdio.h>
#include <string.h>
#include "user.h" 

// условные данные
static struct User data[DATA_COUNT] = {{"Tom", 39}, {"Bob", 43}, {"Sam", 29}, {"Alice", 35}, {"Kate", 28}};

void getUsers(struct User* buffer)
{
    // копируем в буфер данные
    memcpy(buffer, data, DATA_COUNT * sizeof(struct User));
}

Прежде всего здесь определены сами данные в виде массива структур User размером DATA_COUNT

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

И пусть у нас есть основной файл программы - app.c, в котором получаем данные:

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

extern void getUsers(struct User*);

int main(void)
{
  // определяем буфер для данных
  struct User buffer[DATA_COUNT];

  // получаем данные
  getUsers(buffer);

  for(int i =0; i < DATA_COUNT; i++)
  {
    printf("%s - %u\n", buffer[i].name, buffer[i].age);
  }
}

Здесь вызываем функцию getUsers из файла user.c и передаем в нее буфер. В данном случае буфер определен как автоматическая переменная, которая хранится в стеке. Но это не столь важно, подобный буфер может быть определен и в статической и в динамической памяти. И после выполнения функции буфер будет содержать скопированные данные. Пример работы программы:

c:\C>gcc -Wall -pedantic app.c user.c -o app & app
Tom - 39
Bob - 43
Sam - 29
Alice - 35
Kate - 28

c:\C>

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

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