Возврат массива с использованием C


153

Я относительно новичок в C, и мне нужна помощь с методами работы с массивами. Исходя из Java-программирования, я привык говорить int [] method(), чтобы возвращать массив. Тем не менее, я обнаружил, что с C вы должны использовать указатели для массивов, когда вы возвращаете их. Будучи новым программистом, я действительно не понимаю этого вообще, даже с многочисленными форумами, которые я просматривал.

По сути, я пытаюсь написать метод, который возвращает массив символов в C. Я предоставлю метод (давайте назовем его returnArray) с массивом. Он создаст новый массив из предыдущего массива и вернет на него указатель. Мне просто нужна помощь о том, как начать и как прочитать указатель, как только он будет отправлен из массива. Любая помощь, объясняющая это, приветствуется.

Предлагаемый формат кода для функции возврата массива

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

Абонент функции

int main(){
 int i=0;
 char array []={1,0,0,0,0,1,1};
 char arrayCount=0;
 char* returnedArray = returnArray(&arrayCount); ///is this correct?
 for (i=0; i<10;i++)
  printf(%d, ",", returnedArray[i]);  //is this correctly formatted?
}

Я еще не проверял это, так как мой компилятор C в данный момент не работает, но я хотел бы выяснить это


Является ли возвращаемый массив известным размером, указанным в примере кода? Единственное другое замечание, которое я вижу, кроме проблем со стеком, упомянутых в ответах, состоит в том, что если ваш возвращаемый массив имеет неопределенный размер, учитывая то, как работают указатели / массивы в C, вы не будете знать, насколько он велик.
странный мир

Да, я всегда знаю размер поступающего массива. Размер входного и выходного массива не изменится.
user1506919

1
Развитие языка Си * - bell-labs.com/usr/dmr/www/chist.html
x4444

Ответы:


225

Вы не можете вернуть массивы из функций в C. Вы также не можете (не должны) делать это:

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

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

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

Опция 1:

динамически распределять память внутри функции (вызывающая сторона отвечает за освобождение ret)

char *foo(int count) {
    char *ret = malloc(count);
    if(!ret)
        return NULL;

    for(int i = 0; i < count; ++i) 
        ret[i] = i;

    return ret;
}

Назовите это так:

int main() {
    char *p = foo(10);
    if(p) {
        // do stuff with p
        free(p);
    }

    return 0;
}

Вариант 2:

заполнить предварительно выделенный буфер, предоставленный вызывающей стороной (вызывающая сторона выделяет bufи передает функции)

void foo(char *buf, int count) {
    for(int i = 0; i < count; ++i)
        buf[i] = i;
}

И назовите это так:

int main() {
    char arr[10] = {0};
    foo(arr, 10);
    // No need to deallocate because we allocated 
    // arr with automatic storage duration.
    // If we had dynamically allocated it
    // (i.e. malloc or some variant) then we 
    // would need to call free(arr)
}

33
Вариант 3: (статический массив)
moooeeeep

5
@moooeeeep: Да, я специально это упустил, чтобы все было просто, но да, вы можете вернуть указатель на статические данные, объявленные внутри функции.
Эд С.

3
@ user1506919: На самом деле я бы предпочел вариант 2, так как ясно, кто выделяет и освобождает память, но я добавлю для вас пример.
Эд С.

7
Вариант 4. Возвращение структуры, содержащей массив фиксированного размера.
Тодд Леман

2
Вариант 5: вернуть объединение, которое содержит массив фиксированного размера.
sqr163

27

Обработка массивов в С сильно отличается от обработки в Java, и вам придется соответствующим образом изменить свое мышление. Массивы в C не являются объектами первого класса (то есть выражение массива не сохраняет свою «массивность» в большинстве контекстов). В Си выражение типа «массив N-элементов T» будет неявно преобразовано («распад») в выражение типа «указатель на T», за исключением случаев, когда выражение массива является операндом sizeofили унарных &операторов, или если Выражение массива - это строковый литерал, используемый для инициализации другого массива в объявлении.

Среди прочего, это означает, что вы не можете передать выражение массива функции и получить его как тип массива ; функция фактически получает тип указателя:

void foo(char *a, size_t asize)
{
  // do something with a
}

int bar(void)
{
  char str[6] = "Hello";
  foo(str, sizeof str);
}

При вызове fooвыражение strпреобразуется из типа char [6]в char *, поэтому вместо параметра fooобъявляется первый параметр . Так как выражение массива является операндом оператора, оно не преобразуется в тип указателя, поэтому вы получаете количество байтов в массиве (6). char *achar a[6]sizeof strsizeof

Если вам действительно интересно, вы можете прочитать « Развитие языка Си» Денниса Ричи, чтобы понять, откуда это лечение.

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

Самый безопасный метод для вызывающего - определить массив и передать его адрес и размер функции, которая должна записать в него:

void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)
{
  ...
  dstArray[i] = some_value_derived_from(srcArray[i]);
  ...
}

int main(void)
{
  char src[] = "This is a test";
  char dst[sizeof src];
  ...
  returnArray(src, sizeof src, dst, sizeof dst);
  ...
}

Другой метод заключается в том, что функция динамически выделяет массив и возвращает указатель и размер:

char *returnArray(const char *srcArray, size_t srcSize, size_t *dstSize)
{
  char *dstArray = malloc(srcSize);
  if (dstArray)
  {
    *dstSize = srcSize;
    ...
  }
  return dstArray;
}

int main(void)
{
  char src[] = "This is a test";
  char *dst;
  size_t dstSize;

  dst = returnArray(src, sizeof src, &dstSize);
  ...
  free(dst);
  ...
}

В этом случае вызывающая сторона отвечает за освобождение массива с помощью freeфункции библиотеки.

Обратите внимание, что dstв приведенном выше коде это простой указатель на char, а не указатель на массив char. Указатель C и семантика массива таковы, что вы можете применить оператор индекса []к выражению типа массива или типа указателя; оба src[i]и dst[i]получат доступ к i'-ому элементу массива (даже если srcимеет только тип массива).

Вы можете объявить указатель на массив из N элементов Tи сделать нечто подобное:

char (*returnArray(const char *srcArr, size_t srcSize))[SOME_SIZE]
{
  char (*dstArr)[SOME_SIZE] = malloc(sizeof *dstArr);
  if (dstArr)
  {
    ...
    (*dstArr)[i] = ...;
    ...
  }
  return dstArr;
}

int main(void)
{
  char src[] = "This is a test";
  char (*dst)[SOME_SIZE];
  ...
  dst = returnArray(src, sizeof src);
  ...
  printf("%c", (*dst)[j]);
  ...
}

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


2
Ваша ссылка на «разработку C» не работает ... похоже, она должна направить нас сюда: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso

@ Кундор: Получается barуказатель, а не массив. В контексте объявления параметров функции, T a[N]и T a[]оба рассматриваются как T *a.
Джон Боде

@JohnBode: ты прав! По некоторым причинам я думал, что массивы фиксированного размера были переданы в стеке. Я вспоминаю случай, когда много лет назад я обнаружил, что размер массива должен быть указан в сигнатуре параметра, но я, должно быть, запутался.
Ник Маттео

@JohnBode, во второй части кода первая строка: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)последний параметр должен иметь size_tтип not char.
Сейфи

11

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

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    typedef
    struct 
    {
        char v[10];
    } CHAR_ARRAY;



    CHAR_ARRAY returnArray(CHAR_ARRAY array_in, int size)
    {
        CHAR_ARRAY returned;

        /*
        . . . methods to pull values from array, interpret them, and then create new array
        */

        for (int i = 0;  i < size; i++ )
            returned.v[i] = array_in.v[i] + 1;

        return returned; // Works!
    } 




    int main(int argc, char * argv[])
    {
        CHAR_ARRAY array = {1,0,0,0,0,1,1};

        char arrayCount = 7;

        CHAR_ARRAY returnedArray = returnArray(array, arrayCount); 

        for (int i = 0; i < arrayCount; i++)
            printf("%d, ", returnedArray.v[i]);  //is this correctly formatted?

        getchar();
        return 0;
    }

Я приглашаю комментировать сильные и слабые стороны этой техники. Я не удосужился сделать это.


1
Непонятно, почему это не принятый ответ. Вопрос был не в том, можно ли вернуть указатель на массив.
Фрэнк Пук

Выделена ли память для CHAR_ARRAY returnedкучи? Это, конечно, не может в стеке (в кадре стека returnArray()правильно?
Минь Чан

9

Как насчет этого ужасно злого внедрения?

array.h

#define IMPORT_ARRAY(TYPE)    \
    \
struct TYPE##Array {    \
    TYPE* contents;    \
    size_t size;    \
};    \
    \
struct TYPE##Array new_##TYPE##Array() {    \
    struct TYPE##Array a;    \
    a.contents = NULL;    \
    a.size = 0;    \
    return a;    \
}    \
    \
void array_add(struct TYPE##Array* o, TYPE value) {    \
    TYPE* a = malloc((o->size + 1) * sizeof(TYPE));    \
    TYPE i;    \
    for(i = 0; i < o->size; ++i) {    \
        a[i] = o->contents[i];    \
    }    \
    ++(o->size);    \
    a[o->size - 1] = value;    \
    free(o->contents);    \
    o->contents = a;    \
}    \
void array_destroy(struct TYPE##Array* o) {    \
    free(o->contents);    \
}    \
TYPE* array_begin(struct TYPE##Array* o) {    \
    return o->contents;    \
}    \
TYPE* array_end(struct TYPE##Array* o) {    \
    return o->contents + o->size;    \
}

main.c

#include <stdlib.h>
#include "array.h"

IMPORT_ARRAY(int);

struct intArray return_an_array() {
    struct intArray a;
    a = new_intArray();
    array_add(&a, 1);
    array_add(&a, 2);
    array_add(&a, 3);
    return a;
}

int main() {
    struct intArray a;
    int* it;
    int* begin;
    int* end;
    a = return_an_array();
    begin = array_begin(&a);
    end = array_end(&a);
    for(it = begin; it != end; ++it) {
        printf("%d ", *it);
    }
    array_destroy(&a);
    getchar();
    return 0;
}

2
Это чертовски вкусно, чтобы вызвать у меня любопытство. Не могли бы вы рассказать немного больше о том, что вы там делали, или предложить почитать эту вкусность, которую вы называете? Заранее спасибо.
Unheilig

1
@Unheilig - обратите внимание, что в этом есть потенциальные ошибки, это было просто доказательством концепции. Тем не менее, трюк возвращает structв качестве контейнера / объекта массива. Думайте об этом как о C ++ std :: vector. Препроцессор расширил бы intверсию этого до struct intArray { int* contents; int size; };.
пироспад

1
Мне нравится подход. за: это общее решение; Против: интенсивное решение для памяти. Не оптимально для векторов с заданными размерами. В любом случае это может быть улучшено с начальным распределением размера. Я определенно добавил бы некоторую проверку распределения. Очень хорошее предложение для начала :)
уркон

Объектно-ориентированный esk prepossessing mix-mash. Мне это нравится.
Джек Гиффин

6

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

char * returnArray(char *arr, int size) {
    char *new_arr = malloc(sizeof(char) * size);
    for(int i = 0; i < size; ++i) {
        new_arr[i] = arr[i];
    }
    return new_arr;
}

int main() {

    char arr[7]= {1,0,0,0,0,1,1};
    char *new_arr = returnArray(arr, 7);

    // don't forget to free the memory after you're done with the array
    free(new_arr);

}

2
В newСи нет оператора. Это C ++.
Эрик Постпишил

1
И sizeof(char)гарантированно будет 1, так что в этом случае вы можете отказаться от этого malloc.
Эд С.

Итак, если бы я хотел распечатать содержимое нового массива, мог бы я просто сделать оператор 'printf', но заменить 'returnArray' на 'arr'?
user1506919

Вы не вызываете функцию должным образом (только один аргумент, когда подпись требует два).
Эд С.

Вы проходите мимо &arr. Вы хотите arrбыть char *, и передать это в использовании arr.
Крис

4

Вы можете сделать это, используя кучную память (посредством вызова malloc () ), как и другие ответы, представленные здесь, но вы всегда должны управлять памятью (используйте функцию free () каждый раз, когда вы вызываете свою функцию). Вы также можете сделать это со статическим массивом:

char* returnArrayPointer() 
{
static char array[SIZE];

// do something in your array here

return array; 
}

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

int main() 
{
char* myArray = returnArrayPointer();
/* use your array here */
/* don't worry to free memory here */
}

В этом примере вы должны использовать статическое ключевое слово в определении массива, чтобы установить на время приложения время жизни массива, чтобы оно не уничтожалось после оператора return. Конечно, таким образом вы занимаете SIZE байтов в своей памяти на весь срок службы приложения, так что измеряйте его правильно!


2

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

void returnArray(int size, char *retArray)
{
  // work directly with retArray or memcpy into it from elsewhere like
  // memcpy(retArray, localArray, size); 
}

#define ARRAY_SIZE 20

int main(void)
{
  char foo[ARRAY_SIZE];
  returnArray(ARRAY_SIZE, foo);
}

0

Вы можете использовать код как это:

char *MyFunction(some arguments...)
{
    char *pointer = malloc(size for the new array);
    if (!pointer)
        An error occurred, abort or do something about the error.
    return pointer; // Return address of memory to the caller.
}

Когда вы это сделаете, память должна быть позже освобождена, передав адрес свободным.

Есть и другие варианты. Подпрограмма может вернуть указатель на массив (или часть массива), который является частью некоторой существующей структуры. Вызывающая сторона может передать массив, а процедура просто записывает в массив, а не выделяет место для нового массива.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.