Нередко возникает необходимость в получении большого набора данных или сложных по структуре данных фиксированной длины, которые могут изменяться в процессе работы программы. С одной стороны, мы могли бы определить данные в статической памяти и возвращать указатель на них. Но в том случае потребитель данных может столкнуться с несогласованностью данных - когда он получает одни данные и опирается на них, а тем временем данные где-то извне изменяются. Что особенно может быть характерно для многопточных систем, где разные потоки могут обращаться к общих данным и изменить их. Возвратить эти данные напрямую из функции в виде структуры тоже может быть не самым лучшим вариантом, поскольку структура копируется в стек, а стек ограничен поэтому для больших данных данных такой способ не очень подходит.
И в качестве решения здесь может быть определение буфера для получения данных на стороне компонента-приемника данных. Компонент-источник данных определяет функцию, которая принимает данный буфер и копирует в него данные. В общем случае это выглядит следующим образом. Код компонента-источника данных:
#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>
Таким образом, компонент-приемник может быстро и с помощью одного параметра получить большие и сложные по структуре данные. Приемнику данных достаточно предоставить буфер нужной длины. Если к данным обращаются несколько потоков, то каждый предоставляет свой собственный буфер и дальше работает с этим буфером, поэтому данный подход безопасен при использовании в многопоточных программах. Однако компоненту-приемнику необходимо знать размер данных, что не всегда возможно.