Иногда бывает необходимо получить большие или сложные по структуре данных неизвестной длины, которые могут изменяться в процессе работы программы. С одной стороны, мы могли бы определить данные в статической памяти и возвращать указатель на них. Но в том случае потребитель данных может столкнуться с несогласованностью данных - когда он получает одни данные и опирается на них, а тем временем данные где-то извне изменяются. Что особенно может быть характерно для многопточных систем, где разные потоки могут обращаться к общих данным и изменить их. Возвратить эти данные напрямую из функции в виде структуры тоже нельзя, поскольку размер данных неизвестен, а стек ограничен по размеру.
Определение буфера на стороне приемника данных не является решением, поскольку компонент-приемник данных должен выполнить дополнительные действия, чтобы узнать количество данных. Но даже если он узнает количество данных, оно может измениться к тому моменту, когда он выполнит непосредственно запрос данных.
Решением может быть определение буфера для данных на стороне компонента-источника данных, поскольку только компонент-источник знает точный размер данных в текущий момент. Компонент-источник данных также определяет функцию, которая возвращает буфер со скопированными данными и его размер. А компонент-приемник вызывает функцию и получает буфер и его размер.
В плане реализации могут быть вариации, как возвращать буфер и количество считанных данных. Можно возвращать через выходные параметры, либо как результат функции. В общем случае возвращение черех выходные параметры выглядит следующим образом. Код компонента-источника данных:
#define DATA_SIZE 256 // некоторые данные static char data[DATA_SIZE] // функция, копирует в буфер данные и возвращает через параметры буфер и его размер void getData(char** buffer, int* size) { *size = DATA_SIZE; *buffer = malloc(size); memcpy(*buffer, data, size); // копируем данные }
Компонент-источник данных выделяет для буфера некоторую область в динамической памяти и в нее копирует данные. Буфер и его размер возвращаются через выходные параметры. Обратите внимание, что первый параметр - buffer определен как указатель на указатель. То есть грубо говоря в функцию будет передаваться адрес указателя. И по этому адресу сохраняется указатель на выделенную память.
Код компонента-приемника данных:
// указатель для буфера char* buffer; int size; getData(&buffer, &size); // получаем данные и их размер // работа с буфером free(buffer); // освобождаем память
Компонент-приемник вызывает функцию для получения данных и передает в нее указатель для адреса буфера и адрес переменной-размера буфера. После получения и работы с данными компонент-приемник освобождает память (так как буфер выделен в динамической памяти).
В качестве альтернативы можно возвращать буфер и размер данных как результат функции в виде структуры:
/* Общая структура результата */ struct DataList{ char* buffer; // буфер данных size_t size; // размер данных } /* Код источника-приемника данных*/ #define DATA_SIZE 256 // некоторые данные static char data[DATA_SIZE] // функция, копирует в буфер данные и возвращает через структуру DataList struct DataList getData() { struct DataList result; result.size = DATA_SIZE; result.buffer = malloc(size); memcpy(result.buffer, data, result.size); // копируем данные } /***********************************/ /* Код компонента-приемника данных*/ struct DataList result = getData(); // получаем данные и их размер // работа с буфером free(result.buffer); // освобождаем память
Подобный подход удобен в многопоточных системах, так как каждый поток, получив данные, работает с ними независимо от других потоков, и можно не беспокоиться, что данные извне где-то изменяться. Соотвественно достигается согласованность данных, пока с ними работает компонент-приемник данных. В то же время компонент-приемник должен следить за освобождением памяти. Рассмотрим данные варианты на небольшом примере.
Пусть у нас есть заголовочный файл user.h, где определена структура User, которая представляет данные:
// структура представляет данные struct User{ char* name; unsigned age; }; // структура представляет возвращаемый результат struct UsersList { struct User* buffer; size_t count; };
Кроме того, здесь определена структура UsersList, которая будет использоваться для определения результата. Она хранит буфер для считывания структур User и количество считанных структур.
Пусть также определен файл user.c, который определяет компонент-источник данных:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include "user.h" // условные данные static struct User data[] = {{"Tom", 39}, {"Bob", 43}, {"Sam", 29}, {"Alice", 35}, {"Kate", 28}}; struct UsersList getUsers() { // определяем результат struct UsersList result; // общее количество данных в байтах size_t data_size = sizeof(data); // получаем количество структур result.count = data_size / sizeof(struct User); // выделяем память для буфера result.buffer = malloc(data_size); // если не удалось выделить память assert(result.buffer!=NULL && "Unable to allocate a memory"); // копируем в буфер данные memcpy(result.buffer, data, data_size); // возвращаем структуру return result; }
Прежде всего здесь определены сами данные в виде массива data, которые хранит набор структур User и размер которого неизвестен извне.
Функция getUsers определяет возвращаемый результат в виде структуры UsersList. В самой функции получаем размер данных и количество структур. Устанавливаем у возвращаемой структуры поле count - количество структур в буфере. Далее выделяем динамическую память для буфера и сохраняем адрес указателя в поле buffer в возвращаемой структуре и с помощью библиотечной функции memcpy копируем данные в выделенную динамическую память. В конце возвразаем структуру UsersList
И пусть у нас есть основной файл программы - app.c, в котором получаем данные:
#include <stdio.h> #include <stdlib.h> #include "user.h" extern struct UsersList getUsers(); int main(void) { // получаем данные struct UsersList result = getUsers(); // работа с полученными данными for(int i =0; i < result.count; i++) { printf("%s - %u\n", result.buffer[i].name, result.buffer[i].age); } free(result.buffer); // освобождаем память буфера }
Здесь вызываем функцию getUsers из файла user.c и получаем из нее результат в виде структуры UsersList. В итоге ее поле buffer будет содержать адрес буфера со скопированными данными, а поле count - количество структур в наборе. Поскольку буфер выделен в динамической памяти, то в конце освобождаем его память. Пример работы программы:
c:\C>gcc -Wall -pedantic app.c user.c -o app & app Tom - 39 Bob - 43 Sam - 29 Alice - 35 Kate - 28 c:\C>
Другой вариант возвращения данных представляют выходные параметры. Так, пусть в компоненте-источнике данных- в файле user.c определен следующий код:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include "user.h" // условные данные static struct User data[] = {{"Tom", 39}, {"Bob", 43}, {"Sam", 29}, {"Alice", 35}, {"Kate", 28}}; void getUsers(struct User** buffer, size_t* count) { size_t data_size = sizeof(data); // получаем количество структур *count = data_size / sizeof(struct User); // выделяем память для буфера *buffer = malloc(data_size); // если не удалось выделить память assert(*buffer!=NULL && "Unable to allocate a memory"); // копируем в буфер данные memcpy(*buffer, data, data_size); }
Также здесь определены сами данные в виде массива структур User, размер которого неизвестен извне.
Функция getUsers получает в качестве параметра адрес на указатель, в который будет сохраняться указатель на буфер в динамической памяти. Второй параметр представляет указатель на количество структур. В самой функции получаем размер данных и количество структур. Устанавливаем значения указателя count, которые указывает на количество структур в буфере. Далее выделяем динамическую память для буфера и сохраняем адрес указателя на выделенную память в указатель, адрес которого передан в buffer. В конце с помощью библиотечной функции memcpy копируем данные в выделенную динамическую память.
И пусть у нас есть основной файл программы - app.c определяет следующий компонент-приемник данных:
<pre class="brush:c;"> #include <stdio.h> #include <stdlib.h> #include "user.h" extern void getUsers(struct User**, size_t*); int main(void) { // определяем указатель на полученный буфер struct User* buffer; size_t count = 0; // получаем данные getUsers(&buffer, &count); for(int i =0; i < count; i++) { printf("%s - %u\n", buffer[i].name, buffer[i].age); } free(buffer); // освобождаем память буфера }Здесь вызываем функцию getUsers из файла user.c и передаем в нее адрес указателя на буфер и адрес переменной count. После выполнения функции указатель на буфер будет содержать адрес буфера со скопированными данными, а переменная count - количество структур в наборе. И в конце также освобождаем память буфера.
Другая часто используемая схема представляет возвращение буфера через выходной параметр, а количество считанных данных через результат функции. Например, на стороне компонента-источника данных в файле user.c определим следующий код:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include "user.h" // условные данные static struct User data[] = {{"Tom", 22}, {"Bob", 43}, {"Sam", 29}, {"Alice", 35}, {"Kate", 28}}; size_t getUsers(struct User** buffer) { size_t data_size = sizeof(data); // получаем количество структур size_t count = data_size / sizeof(struct User); // выделяем память для буфера *buffer = malloc(data_size); // если не удалось выделить память assert(*buffer!=NULL && "Unable to allocate a memory"); // копируем в буфер данные memcpy(*buffer, data, data_size); // в качестве результата возвращаем количество структур return count; }
Теперь функция getUsers возвращает количество данных напрямую в качестве результата.
В компоненте-приемника данных - в файлеapp.c получим переданные данные:
#include <stdio.h> #include <stdlib.h> #include "user.h" extern size_t getUsers(struct User**); int main(void) { // определяем указатель на полученный буфер struct User* buffer; // получаем данные size_t count = getUsers(&buffer); for(int i =0; i < count; i++) { printf("%s - %u\n", buffer[i].name, buffer[i].age); } free(buffer); // освобождаем память буфера }