Функция, которая возвращает массив строк C - PullRequest
0 голосов
/ 06 декабря 2018

Есть ли способ вернуть массив строк из функции без использования динамического выделения памяти?Функция выглядит примерно так:

char** modify(char original[1000][1000]){ 
    char result[1000][1000];
    // some operations are applied to the original
    // the original is copied to the result
    return result;
}

Ответы [ 5 ]

0 голосов
/ 06 декабря 2018

Хотя вы не можете вернуть тип массива в C, вы можете вернуть struct, содержащий его:

#include <string.h>

#define NSTRINGS 100
#define STR_LEN 100

typedef struct stringtable {
  char table[NSTRINGS][STR_LEN];
} stringtable;

stringtable modify ( const stringtable* const input )
{
  stringtable result;

  memcpy( &result, input, sizeof(result) );

  return result;
}

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

0 голосов
/ 06 декабря 2018

Невозможно вернуть указатель на память, выделенную внутри функции, без динамического выделения.В вашем случае вы разместите result[1000][1000] в стеке в зоне, которая будет освобождена после возврата из функции.Помимо динамического выделения, у вас есть опция передачи буфера в качестве аргумента вашей функции:

void modify(char original[1000][1000], char result[][]) { ... }

Теперь матрица result должна быть размещена вне функции modify, и ее время жизни не будет зависетьна время жизни функции.По сути, вы передаете функции уже выделенную матрицу, в которую будет записан результат.

0 голосов
/ 06 декабря 2018

Вы можете использовать связанный список, начинающийся с первой строки и заканчивающийся последней строкой.

0 голосов
/ 06 декабря 2018

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

В основном result - указатель на первый элемент массива, выделенного в стекепоэтому возврат его и последующая разыменование приведут к неопределенному поведению.

Чтобы обойти эту проблему, есть несколько обходных путей.

Один из них я видел в нескольких проектах, ноЯ не рекомендую это делать, потому что это небезопасно.

char** modify(char original[1000][1000]){ 
    // `result` is static array, which lifetime is equal to the lifetime of the program
    // Calling modify more than one time will result in overwriting of the `result`.
    static char result[1000][1000]; 
    return result;
}

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

void modify(char original[1000][1000], char (*result)[1000]){ 
    result[0][1] = 42; 
    //...
}
void main() {
    char result[1000][1000];
    modify(someOriginal, result);
}

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

0 голосов
/ 06 декабря 2018

В C объект имеет одну из четырех длительностей хранения (также называемых продолжительностью жизни): статическую, потоковую, автоматическую и распределенную (C 2018 6.2.4 1).

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

Объекты с выделенной продолжительностью хранения сохраняются до освобождения, но вы попросилиисключите их.

Длительность хранения потока, скорее всего, не применима к вашей ситуации или фактически эквивалентна длительности статического хранения, о которой я расскажу ниже.

Это означает, что вы можете выбрать следующие варианты:

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

Таким образом, типичное решение выглядит следующим образом:

size_t HowMuchSpaceIsNeeded(char original[1000][1000])
{
    … Calculate size.
    return SizeNeeded;
}

void modify(char destination[][1000], char original[1000][1000])
{
    … Put results in destination.
}

Вариантдля обеспечения безопасности:

void modify(char destination[][1000], size_t size, char original[1000][1000])
{
    if (size < amount needed)
        … Report error (possibly by return value, or program abort).
    … Put results in destination.
}

Затем вызывающая сторона делает что-то вроде:

size_t size = HowMuchSpaceIsNeeded(original);
char (*results)[1000] = malloc(size);
if (!results)
    … Report error.
modify(results, size, original)
… Work with results.
free(results);

Как Давистор Примечания , функция может вернутьмассив встроен в структуру.С точки зрения семантики C это позволяет избежать проблемы времени жизни объекта, возвращая значение, а не объект.(Все содержимое структуры является значением структуры.) С точки зрения фактической аппаратной реализации, оно в значительной степени эквивалентно методу вызывающего прохода объекта, описанному выше.(Рассуждения здесь основаны на логике работы компьютеров, а не на спецификации C: чтобы функция возвращала значение, для представления которого требуется много места, вызывающая сторона должна предоставить требуемое пространство для вызываемой функции.Обычно вызывающая сторона выделяет место в стеке и предоставляет его вызываемой функции.Это может быть быстрее, чем malloc, но может также использовать значительное количество стекового пространства.Обычно мы избегаем использования значительных объемов стекового пространства, чтобы избежать переполнения стека.

...