Zwracanie tablicy przy użyciu języka C

153

Jestem stosunkowo nowy w C i potrzebuję pomocy w metodach radzenia sobie z tablicami. Pochodząc z programowania w Javie, jestem przyzwyczajony do mówienia int [] method()w celu zwrócenia tablicy. Jednak odkryłem, że w C musisz używać wskaźników do tablic, kiedy je zwracasz. Będąc nowym programistą, naprawdę w ogóle tego nie rozumiem, nawet z wieloma forami, które przeglądałem.

Zasadniczo próbuję napisać metodę, która zwraca tablicę char w C. Dostarczę metodę (nazwijmy ją returnArray) z tablicą. Utworzy nową tablicę z poprzedniej tablicy i zwróci do niej wskaźnik. Potrzebuję tylko pomocy, jak to rozpocząć i jak odczytać wskaźnik po wysłaniu go z tablicy. Każda pomoc w wyjaśnieniu tego jest mile widziana.

Proponowany format kodu dla funkcji zwracającej tablicę

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?
} 

Wzywający funkcji

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?
}

Nie testowałem tego jeszcze, ponieważ mój kompilator C w tej chwili nie działa, ale chciałbym to rozgryźć

user1506919
źródło
Czy tablica zwracana ma znany rozmiar, jak wskazano w przykładowym kodzie? Jedynym innym problemem, który widzę, poza problemami ze stosem wspomnianymi w odpowiedziach, jest to, że jeśli twoja tablica zwrotna ma nieokreślony rozmiar, biorąc pod uwagę sposób działania wskaźników / tablic w C, nie będziesz wiedział, jaka jest duża.
strangefreeworld
Tak, zawsze znam rozmiar przychodzącej tablicy. Rozmiar tablicy wejściowej i wyjściowej nie ulegnie zmianie.
user1506919
1
Rozwój języka C * - bell-labs.com/usr/dmr/www/chist.html
x4444,

Odpowiedzi:

225

Nie możesz zwracać tablic z funkcji w C. Nie możesz (nie powinieneś) tego robić:

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 jest tworzony z automatycznym czasem przechowywania i odniesienia do niego staną się nieważne, gdy opuści swój zakres deklarowania, tj. gdy funkcja powróci.

Będziesz musiał dynamicznie przydzielać pamięć wewnątrz funkcji lub wypełnić wstępnie przydzielony bufor dostarczony przez wywołującego.

Opcja 1:

dynamicznie alokować pamięć wewnątrz funkcji (wywołujący odpowiedzialny za zwalnianie 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;
}

Nazwij to tak:

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

    return 0;
}

Opcja 2:

wypełnij wstępnie przydzielony bufor dostarczony przez dzwoniącego (dzwoniący przydziela bufi przekazuje do funkcji)

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

I nazwij to tak:

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)
}
Ed S.
źródło
33
Opcja 3: (tablica statyczna)
moooeeeep
5
@moooeeeep: Tak, celowo pominąłem to, aby wszystko było proste, ale tak, możesz zwrócić wskaźnik do danych statycznych zadeklarowanych z poziomu funkcji.
Ed S.,
3
@ user1506919: Właściwie wolałbym opcję 2, ponieważ jest jasne, kto przydziela i zwalnia pamięć, ale dodam dla ciebie przykład.
Ed S.,
7
Opcja 4: Zwróć strukturę, która zawiera tablicę o stałym rozmiarze.
Todd Lehman
2
Opcja 5: Zwróć sumę zawierającą tablicę o stałym rozmiarze.
sqr163
27

Sposób traktowania tablic w języku C bardzo różni się od sposobu traktowania w Javie i będziesz musiał odpowiednio dostosować swoje myślenie. Tablice w języku C nie są obiektami pierwszej klasy (to znaczy, że wyrażenie tablicowe nie zachowuje swojej „macierzy” w większości kontekstów). W języku C wyrażenie typu „tablica elementów N T” zostanie niejawnie przekonwertowane („rozpad”) na wyrażenie typu „wskaźnik do T”, z wyjątkiem sytuacji, gdy wyrażenie tablicowe jest operandem sizeofjednoargumentowym lub& operatorów lub, lub jeśli wyrażenie tablicowe to literał łańcuchowy używany do inicjalizacji innej tablicy w deklaracji.

Oznacza to między innymi, że nie można przekazać wyrażenia tablicowego do funkcji i odebrać je jako typ tablicowy ; funkcja faktycznie otrzymuje typ wskaźnika:

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

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

W wywołaniu do foowyrażenie strjest konwertowane z typu char [6]na char *, dlatego zamiast parametru foodeklarowany jest pierwszy parametr . W , ponieważ wyrażenie tablicowe jest operandem operatora, nie jest konwertowane na typ wskaźnikowy, więc otrzymujesz liczbę bajtów w tablicy (6). char *achar a[6]sizeof strsizeof

Jeśli jesteś naprawdę zainteresowany, możesz przeczytać książkę Dennisa Ritchiego The Development of the C Language, aby zrozumieć, skąd pochodzi ta terapia.

W rezultacie funkcje nie mogą zwracać typów tablicowych, co jest w porządku, ponieważ wyrażenia tablicowe również nie mogą być celem przypisania.

Najbezpieczniejszą metodą jest zdefiniowanie przez wywołującego tablicy i przekazanie jej adresu i rozmiaru do funkcji, która ma do niej pisać:

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);
  ...
}

Inną metodą jest dynamiczne przydzielanie tablicy przez funkcję i zwracanie wskaźnika i rozmiaru:

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);
  ...
}

W tym przypadku wywołujący jest odpowiedzialny za zwolnienie tablicy za pomocą freefunkcji bibliotecznej.

Zauważ, że dstw powyższym kodzie jest prosty wskaźnik do char, a nie wskaźnik do tablicy char. Wskaźnik C i semantyka tablicy są takie, że można zastosować operator indeksu dolnego []do wyrażenia typu tablicowego lub typu wskaźnika; oba src[i]i dst[i]uzyskają dostęp do i'-tego elementu tablicy (nawet jeśli srcma tylko typ tablicy).

Państwo może zadeklarować wskaźnik do tablicy N-pierwiastkowej Ti zrobić coś podobnego:

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]);
  ...
}

Kilka wad z powyższym. Po pierwsze, starsze wersje języka C oczekują, SOME_SIZEże będzie to stała czasu kompilacji, co oznacza, że ​​funkcja będzie działać zawsze tylko z jednym rozmiarem tablicy. Po drugie, przed zastosowaniem indeksu dolnego należy usunąć odwołanie ze wskaźnika, który zaśmieca kod. Wskaźniki do tablic działają lepiej, gdy masz do czynienia z tablicami wielowymiarowymi.

John Bode
źródło
2
Twój link do „rozwoju C” został uszkodzony ... wygląda na to, że powinien nas skierować tutaj: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso
@Kundor: Otrzymuje barwskaźnik, a nie tablicę. W kontekście deklaracji parametru funkcji T a[N]i T a[]oba są traktowane jako T *a.
John Bode,
@JohnBode: Masz rację! Z jakiegoś powodu pomyślałem, że tablice o stałym rozmiarze zostały przekazane na stosie. Przypominam sobie sytuację, kiedy wiele lat temu odkryłem, że rozmiar tablicy musiał być określony w podpisie parametru, ale musiałem się pomylić.
Nick Matteo,
@JohnBode, w drugiej części kodu pierwsza linia: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)ostatni parametr powinien być size_ttypu not char.
Seyfi
11

Nie twierdzę, że jest to najlepsze lub preferowane rozwiązanie danego problemu. Warto jednak pamiętać, że funkcje mogą zwracać struktury. Chociaż funkcje nie mogą zwracać tablic, tablice mogą być opakowane w struktury, a funkcja może zwrócić strukturę, przenosząc w ten sposób tablicę. Działa to w przypadku tablic o stałej długości.

    #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;
    }

Zapraszam do komentowania mocnych i słabych stron tej techniki. Nie pofatygowałem się, żeby to zrobić.

Nie rozumiem
źródło
1
nie jest jasne, dlaczego nie jest to akceptowana odpowiedź. Nie chodziło o to, czy można zwrócić wskaźnik do tablicy.
Frank Puck
Czy pamięć jest przydzielona CHAR_ARRAY returnedna stercie? Z pewnością nie może być na stosie (w ramce stosu po returnArray()prawej?
Minh Tran
9

A co z tą cudownie złą implementacją?

tablica.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;
}
pirospada
źródło
2
To jest tak diabelnie smaczne, że wzbudziło moją ciekawość. Czy możesz wyjaśnić trochę więcej, co tam zrobiłeś, lub może zasugerować odczytanie tej pyszności, którą nazywasz? Z góry dziękuję.
Unheilig
1
@Unheilig - Zauważ, że są w tym pewne potencjalne błędy, to tylko dowód koncepcji. To powiedziawszy, sztuczka polega na zwróceniu a structjako kontenera / obiektu tablicy. Pomyśl o tym jak o std :: vector w C ++. Preprocesor rozszerzyłby intwersję tego do struct intArray { int* contents; int size; };.
pirospada
1
Podoba mi się to podejście. pro: to jest ogólne rozwiązanie; contra: rozwiązanie wymagające dużej ilości pamięci. Nieoptymalne dla wektorów o kownach. W każdym razie można to ulepszyć poprzez wstępną alokację rozmiaru. Zdecydowanie dodałbym sprawdzenie alokacji. Bardzo dobra propozycja na początek :)
urkon
Zorientowany obiektowo esk prepossessing mix-mash. Lubię to.
Jack Giffin
6

W twoim przypadku tworzysz tablicę na stosie i gdy opuścisz zakres funkcji, tablica zostanie cofnięta. Zamiast tego utwórz tablicę alokowaną dynamicznie i zwróć do niej wskaźnik.

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);

}
Człowiek jednej drogi
źródło
2
Nie ma newoperatora w C. To jest C ++.
Eric Postpischil
1
I sizeof(char)na pewno 1tak będzie , więc w tym przypadku możesz upuścić ten kawałek malloc.
Ed S.,
ok, więc gdybym chciał wydrukować zawartość nowej tablicy, czy mógłbym po prostu wykonać instrukcję „printf”, ale zamienić „returnArray” na „arr”?
user1506919
Nie wywołujesz funkcji poprawnie (tylko jeden argument, gdy podpis wymaga dwóch).
Ed S.,
Przechodzisz &arr. Chcesz arrbyć a char *, i przekaż to w użyciu arr.
chris
4

Możesz to zrobić używając pamięci sterty (poprzez wywołanie malloc () ), tak jak inne podane tutaj odpowiedzi, ale zawsze musisz zarządzać pamięcią (użyj funkcji free () za każdym razem, gdy wywołujesz swoją funkcję). Możesz to również zrobić za pomocą tablicy statycznej:

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

// do something in your array here

return array; 
}

Możesz go używać bez martwienia się o zarządzanie pamięcią.

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

W tym przykładzie należy użyć słowa kluczowego static w definicji tablicy, aby ustawić okres istnienia tablicy na długość aplikacji, aby nie został zniszczony po instrukcji return. Oczywiście w ten sposób zajmujesz SIZE bajtów w swojej pamięci przez cały okres użytkowania aplikacji, więc odpowiednio ją dopasuj!

mengo
źródło
2

Twoja metoda zwróci lokalną zmienną stosu, która zakończy się poważnym niepowodzeniem. Aby zwrócić tablicę, utwórz tablicę poza funkcją, przekaż ją przez adres do funkcji, a następnie zmodyfikuj ją lub utwórz tablicę na stercie i zwróć tę zmienną. Oba będą działać, ale pierwsza nie wymaga żadnej dynamicznej alokacji pamięci, aby działała poprawnie.

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);
}
Michael Dorgan
źródło
0

Możesz użyć takiego kodu:

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.
}

Kiedy to zrobisz, pamięć powinna zostać później zwolniona, przekazując adres do zwolnienia.

Istnieją inne opcje. Procedura może zwrócić wskaźnik do tablicy (lub części tablicy), która jest częścią jakiejś istniejącej struktury. Obiekt wywołujący może przekazać tablicę, a procedura po prostu zapisuje do tablicy, zamiast przydzielać miejsce na nową tablicę.

Eric Postpischil
źródło