Повернення масиву за допомогою C


153

Я відносно новий в C і мені потрібна допомога щодо методів роботи з масивами. Виходячи з програмування Java, я звик говорити int [] method(), щоб повернути масив. Однак я з’ясував, що з C ви повинні використовувати вказівники для масивів, коли ви повертаєте їх. Будучи новим програмістом, я насправді цього зовсім не розумію, навіть із багатьох форумів, які я переглянув.

В основному я намагаюся написати метод, який повертає масив char у 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 зараз не працює, але я хотів би це зрозуміти


Чи є масив повернення відомого розміру, як зазначено у вашому зразку коду? Єдине інше, що я бачу, крім проблем зі стеком, згаданих у відповідях, - це те, що якщо ваш масив повернення невизначений розмір, враховуючи спосіб роботи покажчиків / масивів у С, ви не знатимете, наскільки він великий.
unknownfreeworld

Так, я знаю розмір вхідного масиву в усі часи. Розміри вхідного та вихідного масиву не змінюються.
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

Обробка масивів C дуже відрізняється від Java, і вам доведеться відповідно налаштувати своє мислення. Масиви на C не є першокласними об'єктами (тобто вираз масиву не зберігає його "масив-ness" у більшості контекстів). У 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оголошується char *aзамість char a[6]. В sizeof str, так як масив вираз є операндом sizeofоператора, він не перетвориться в тип покажчика, так що ви отримаєте кількість байтів в масиві (6).

Якщо вас справді цікавить, ви можете прочитати «Розвиток мови С» Денніса Річі, щоб зрозуміти, звідки походить таке лікування.

Підсумок полягає в тому, що функції не можуть повертати типи масивів, що добре, оскільки вирази масиву також не можуть бути ціллю призначення.

Найбезпечнішим методом є те, що абонент повинен визначити масив та передати його адресу та розмір функції, яка повинна йому написати:

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

@Kundor: Що barотримує вказівник, а не масив. У контексті декларації параметра функції T a[N]і T a[]обидва трактуються як T *a.
Джон Боде

@JohnBode: Ти маєш рацію! Я чомусь подумав, що масиви фіксованого розміру передаються на стек. Я пригадую випадок, багато років тому, коли я виявив, що розмір масиву повинен бути вказаний у підписі параметра, але я, мабуть, плутався.
Нік Маттео

@JohnBode, у другій кодовій частині перший рядок: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)останній параметр повинен бути size_tне типу 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()правильно?
Minh Tran

9

Як щодо цього чудово злого виконання?

масив.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
Мені подобається підхід. pro: це загальне рішення; проти: інтенсивне рішення пам'яті. Не оптимально для векторів розмірів коун. У будь-якому разі це можна покращити за допомогою розподілу вроджених розмірів. Я б дефінітлі додав перевірку розподілу. Дуже гарна пропозиція почати з :)
urkon

Об'єктно-орієнтований еск-міс-меш. Мені це подобається.
Джек Гіффін

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
У newC. немає оператора. Це C ++.
Eric Postpischil

1
І sizeof(char)це гарантовано буде 1, тому в цьому випадку ви можете скинути цей шматочок malloc.
Ред С.

Ок, якщо я хотів би роздрукувати вміст нового масиву, чи можу я просто зробити свій "printf", але замінити "returnArray" на "arr"?
user1506919

Ви не викликаєте функцію належним чином (лише один аргумент, коли для підпису потрібно два).
Ред С.

Ти проїжджаєш &arr. Ви хочете arrстати char *і передайте це в користування arr.
chris

4

Ви можете робити це за допомогою купи пам'яті (через виклик malloc () ), як і інші відповіді, про які повідомляється тут, але ви завжди повинні керувати функцією пам'яті (використовувати безкоштовно () функцію кожного разу, коли ви викликаєте свою функцію). Ви також можете зробити це зі статичним масивом:

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.