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

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

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

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

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

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

#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); // освобождаем память буфера
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850