Rückgabe eines Arrays mit C.

151

Ich bin relativ neu in C und brauche Hilfe bei Methoden, die mit Arrays umgehen. Ich komme aus der Java-Programmierung und bin es gewohnt, sagen int [] method()zu können, um ein Array zurückzugeben. Ich habe jedoch herausgefunden, dass Sie mit C Zeiger für Arrays verwenden müssen, wenn Sie sie zurückgeben. Als neuer Programmierer verstehe ich das überhaupt nicht, selbst bei den vielen Foren, die ich durchgesehen habe.

Grundsätzlich versuche ich, eine Methode zu schreiben, die ein char-Array in C zurückgibt. Ich werde der Methode (nennen wir sie returnArray) ein Array zur Verfügung stellen. Es wird ein neues Array aus dem vorherigen Array erstellt und ein Zeiger darauf zurückgegeben. Ich brauche nur etwas Hilfe, wie man damit anfängt und wie man den Zeiger liest, sobald er aus dem Array gesendet wird. Jede Hilfe, die dies erklärt, ist willkommen.

Vorgeschlagenes Codeformat für die Array-Rückgabefunktion

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

Aufrufer der Funktion

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

Ich habe dies noch nicht getestet, da mein C-Compiler derzeit nicht funktioniert, aber ich möchte dies herausfinden

user1506919
quelle
Hat das Rückgabearray eine bekannte Größe, wie in Ihrem Codebeispiel angegeben? Das einzige andere Problem, das ich neben den in den Antworten erwähnten Stapelproblemen sehe, ist, dass Sie nicht wissen, wie groß Zeiger / Arrays sind, wenn Ihr Rückgabearray eine unbestimmte Größe hat, da Zeiger / Arrays in C funktionieren.
seltsame Freeworld
Ja, ich kenne die Größe des eingehenden Arrays jederzeit. Die Größe des Eingabe- und Ausgabearrays ändert sich nicht.
user1506919
1
Die Entwicklung der C-Sprache * - bell-labs.com/usr/dmr/www/chist.html
x4444

Antworten:

225

Sie können keine Arrays von Funktionen in C zurückgeben. Sie können (sollten) dies auch nicht tun:

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 wird mit automatischer Speicherdauer erstellt und Verweise darauf werden ungültig, sobald sie ihren deklarierenden Bereich verlassen, dh wenn die Funktion zurückkehrt.

Sie müssen den Speicher innerhalb der Funktion dynamisch zuweisen oder einen vom Aufrufer bereitgestellten vorab zugewiesenen Puffer füllen.

Option 1:

Ordnen Sie den Speicher innerhalb der Funktion dynamisch zu (Anrufer, der für die Freigabe verantwortlich ist 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;
}

Nennen Sie es so:

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

    return 0;
}

Option 2:

Füllen Sie einen vom Aufrufer bereitgestellten vorab zugewiesenen Puffer (der Anrufer weist bufdie Funktion zu und übergibt sie an sie).

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

Und nenne es so:

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.
quelle
33
Option 3: (ein statisches Array)
Moooeeeep
5
@moooeeeep: Ja, ich habe das absichtlich weggelassen, um die Dinge einfach zu halten, aber ja, Sie können einen Zeiger auf statische Daten zurückgeben, die innerhalb der Funktion deklariert wurden.
Ed S.
3
@ user1506919: Ich würde eigentlich Option 2 bevorzugen, da klar ist, wer Speicher zuweist und freigibt, aber ich werde ein Beispiel für Sie hinzufügen.
Ed S.
7
Option 4: Geben Sie eine Struktur zurück, die ein Array mit fester Größe enthält.
Todd Lehman
2
Option 5: Geben Sie eine Union zurück, die ein Array mit fester Größe enthält.
sqr163
27

C der Behandlung von Arrays ist sehr verschieden von Java, und Sie werden Ihr Denken entsprechend anpassen müssen. Arrays in C sind keine erstklassigen Objekte (dh ein Array-Ausdruck behält in den meisten Kontexten nicht seine "Array-Ness" bei). In C wird ein Ausdruck vom Typ "N-Element-Array von T" implizit in einen Ausdruck vom Typ "Zeiger auf T" konvertiert ("Zerfall") , es sei denn, der Array-Ausdruck ist ein Operand der sizeofoder unären &Operatoren oder wenn der Der Array-Ausdruck ist ein String-Literal, mit dem ein anderes Array in einer Deklaration initialisiert wird.

Dies bedeutet unter anderem, dass Sie einen Array-Ausdruck nicht an eine Funktion übergeben und als Array-Typ empfangen lassen können . Die Funktion erhält tatsächlich einen Zeigertyp:

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

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

Beim Aufruf von foowird der Ausdruck strvom Typ konvertiertchar [6] inchar * , weshalb der erste Parameter von anstelle von foodeklariert char *awird char a[6]. In sizeof strDa die Array - Ausdruck ein Operand ist , der sizeofBediener, es ist nicht auf einen Zeigertyp umgewandelt, so dass man die Anzahl der Bytes in dem Feld (6) erhalten.

Wenn Sie wirklich interessiert sind, können Sie Dennis Ritchies Die Entwicklung der C-Sprache lesen, um zu verstehen, woher diese Behandlung kommt.

Das Ergebnis ist, dass Funktionen keine Array-Typen zurückgeben können, was in Ordnung ist, da Array-Ausdrücke auch nicht das Ziel einer Zuweisung sein können.

Die sicherste Methode besteht darin, dass der Aufrufer das Array definiert und seine Adresse und Größe an die Funktion übergibt, die darauf schreiben soll:

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

Eine andere Methode besteht darin, dass die Funktion das Array dynamisch zuweist und den Zeiger und die Größe zurückgibt:

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

In diesem Fall ist der Aufrufer dafür verantwortlich, die Zuordnung des Arrays zur freeBibliotheksfunktion aufzuheben.

Beachten Sie, dass dstder obige Code ein einfacher charZeiger auf ein Array von ist char. Die Zeiger- und Array-Semantik von C ist so, dass Sie den Indexoperator []entweder auf einen Ausdruck vom Typ Array oder vom Zeigertyp anwenden können . beide src[i]und dst[i]greifen auf das i'th Element des Arrays zu (obwohl nursrc den Array-Typ hat).

Sie können einen Zeiger auf ein N-Element-Array von deklarieren Tund etwas Ähnliches tun:

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

Mehrere Nachteile mit den oben genannten. Erstens erwarten ältere Versionen von C SOME_SIZEeine Konstante für die Kompilierungszeit, was bedeutet, dass die Funktion immer nur mit einer Arraygröße funktioniert. Zweitens müssen Sie den Zeiger dereferenzieren, bevor Sie den Index anwenden, der den Code überfüllt. Zeiger auf Arrays funktionieren besser, wenn Sie mit mehrdimensionalen Arrays arbeiten.

John Bode
quelle
2
Ihr Link zu "Die Entwicklung von C" ist unterbrochen ... es sieht so aus, als sollte er uns hierher führen: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso
@ Kundor: Was barerhält, ist ein Zeiger, kein Array. Im Rahmen einer Funktion Parameterdeklaration, T a[N]und T a[]sind beide so behandelt , als T *a.
John Bode
@ JohnBode: Du hast recht! Aus irgendeinem Grund dachte ich, dass Arrays mit fester Größe auf dem Stapel übergeben wurden. Ich erinnere mich an eine Gelegenheit vor vielen Jahren, als ich feststellte, dass die Größe eines Arrays in der Parametersignatur angegeben werden musste, aber ich muss verwirrt gewesen sein.
Nick Matteo
@ JohnBode, im zweiten Codeteil erste Zeile: Der void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)letzte Parameter sollte vom size_tTyp nicht sein char.
Seyfi
11

Ich sage nicht, dass dies die beste Lösung oder eine bevorzugte Lösung für das gegebene Problem ist. Es kann jedoch nützlich sein, sich daran zu erinnern, dass Funktionen Strukturen zurückgeben können. Obwohl Funktionen keine Arrays zurückgeben können, können Arrays in Strukturen eingeschlossen werden, und die Funktion kann die Struktur zurückgeben, wodurch das Array mitgeführt wird. Dies funktioniert für Arrays mit fester Länge.

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

Ich lade Sie ein, Kommentare zu den Stärken und Schwächen dieser Technik abzugeben. Ich habe mich nicht darum gekümmert.

Indinfer
quelle
1
unklar, warum dies nicht die akzeptierte Antwort ist. Die Frage war nicht, ob es möglich ist, einen Zeiger auf ein Array zurückzugeben.
Frank Puck
Ist der Speicher CHAR_ARRAY returnedauf dem Heap reserviert ? Es kann sicher nicht das auf dem Stapel sein (im Stapelrahmen von returnArray()rechts?
Minh Tran
9

Wie wäre es mit dieser köstlich bösen Implementierung?

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

Haupt 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;
}
Pyrospade
quelle
2
Das ist teuflisch lecker genug, um meine Neugier zu wecken. Können Sie etwas mehr erklären, was Sie dort oben getan haben, oder vielleicht eine Lektüre zu dieser Köstlichkeit vorschlagen, die Sie nennen? Danke im Voraus.
Unheilig
1
@Unheilig - Beachten Sie, dass dies einige potenzielle Fehler enthält. Dies war nur ein Beweis für das Konzept. Der Trick besteht jedoch darin, a structals Array-Container / Objekt zurückzugeben. Stellen Sie es sich wie einen C ++ std :: vector vor. Der Präprozessor würde die intVersion davon auf erweitern struct intArray { int* contents; int size; };.
Pyrospade
1
Ich mag den Ansatz. Pro: Dies ist eine generische Lösung. contra: speicherintensive Lösung. Nicht optimal für Vektoren mit bekannten Größen. Auf jeden Fall kann dies mit der anfänglichen Größenzuweisung aktualisiert werden. Ich würde definitiv eine Zuordnungsprüfung hinzufügen. Sehr guter Vorschlag, um mit zu beginnen :)
urkon
Objektorientiert-esk einnehmend Mix-Mash. Ich mag das.
Jack Giffin
6

In Ihrem Fall erstellen Sie ein Array auf dem Stapel. Sobald Sie den Funktionsumfang verlassen, wird die Zuordnung des Arrays aufgehoben. Erstellen Sie stattdessen ein dynamisch zugewiesenes Array und geben Sie einen Zeiger darauf zurück.

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

}
Mann der Einbahnstraße
quelle
2
newIn C gibt es keinen Operator. Das ist C ++.
Eric Postpischil
1
Und sizeof(char)ist garantiert 1, also können Sie in diesem Fall das Bit von fallen lassen malloc.
Ed S.
ok also Wenn ich den Inhalt des neuen Arrays ausdrucken wollte, könnte ich dann einfach meine 'printf'-Anweisung ausführen, aber' returnArray 'durch' arr 'ersetzen?
user1506919
Sie rufen die Funktion nicht richtig auf (nur ein Argument, wenn für die Signatur zwei erforderlich sind).
Ed S.
Du gehst vorbei &arr. Du willst arrein sein char *und es weitergeben mit arr.
Chris
4

Sie können dies wie andere hier gemeldete Antworten mithilfe des Heap-Speichers (durch Aufruf von malloc () ) tun , aber Sie müssen den Speicher immer verwalten (verwenden Sie die Funktion free () jedes Mal, wenn Sie Ihre Funktion aufrufen). Sie können dies auch mit einem statischen Array tun:

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

// do something in your array here

return array; 
}

Sie können es dann verwenden, ohne sich um die Speicherverwaltung kümmern zu müssen.

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

In diesem Beispiel müssen Sie das statische Schlüsselwort in der Array-Definition verwenden, um die Lebensdauer des Arrays auf anwendungslang festzulegen, damit es nach der return-Anweisung nicht zerstört wird. Auf diese Weise belegen Sie natürlich GRÖSSE Bytes in Ihrem Speicher für die gesamte Anwendungslebensdauer. Passen Sie die Größe also richtig an!

Mengo
quelle
2

Ihre Methode gibt eine lokale Stapelvariable zurück, die fehlerhaft ausfällt. Um ein Array zurückzugeben, erstellen Sie eines außerhalb der Funktion, übergeben Sie es als Adresse an die Funktion, ändern Sie es dann oder erstellen Sie ein Array auf dem Heap und geben Sie diese Variable zurück. Beide funktionieren, aber die erste erfordert keine dynamische Speicherzuweisung, damit sie ordnungsgemäß funktioniert.

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
quelle
0

Sie können folgenden Code verwenden:

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

Wenn Sie dies tun, sollte der Speicher später freigegeben werden, indem Sie die Adresse an free übergeben.

Es gibt andere Möglichkeiten. Eine Routine kann einen Zeiger auf ein Array (oder einen Teil eines Arrays) zurückgeben, das Teil einer vorhandenen Struktur ist. Der Aufrufer übergibt möglicherweise ein Array, und die Routine schreibt lediglich in das Array, anstatt Speicherplatz für ein neues Array zuzuweisen.

Eric Postpischil
quelle