Renvoyer un tableau en utilisant C

153

Je suis relativement nouveau en C et j'ai besoin d'aide pour les méthodes traitant des tableaux. Issu de la programmation Java, j'ai l'habitude de pouvoir dire int [] method()pour renvoyer un tableau. Cependant, j'ai découvert qu'avec C, vous devez utiliser des pointeurs pour les tableaux lorsque vous les renvoyez. En tant que nouveau programmeur, je ne comprends vraiment pas du tout cela, même avec les nombreux forums que j'ai parcourus.

Fondamentalement, j'essaie d'écrire une méthode qui renvoie un tableau de caractères en C. Je vais fournir la méthode (appelons-la returnArray) avec un tableau. Il créera un nouveau tableau à partir du tableau précédent et y renverra un pointeur. J'ai juste besoin d'aide sur la façon de démarrer et de lire le pointeur une fois qu'il est envoyé hors du tableau. Toute aide expliquant cela est appréciée.

Format de code proposé pour la fonction de retour de tableau

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

Appelant de la fonction

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

Je n'ai pas encore testé cela car mon compilateur C ne fonctionne pas pour le moment, mais j'aimerais comprendre cela

user1506919
la source
Le tableau de retour a-t-il une taille connue, comme indiqué dans votre exemple de code? Le seul autre problème que je vois en plus des problèmes de pile mentionnés dans les réponses est que si votre tableau de retour est d'une taille indéterminée, étant donné la façon dont les pointeurs / tableaux fonctionnent en C, vous ne saurez pas quelle est sa taille.
étrangefreeworld
Oui, je connais à tout moment la taille du tableau entrant. La taille du tableau d'entrée et de sortie ne changera pas.
user1506919
1
Le développement du langage C * - bell-labs.com/usr/dmr/www/chist.html
x4444

Réponses:

225

Vous ne pouvez pas retourner de tableaux à partir de fonctions en 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 est créé avec une durée de stockage automatique et les références à celui-ci deviendront invalides une fois qu'il aura quitté sa portée de déclaration, c'est-à-dire lorsque la fonction sera renvoyée.

Vous devrez allouer dynamiquement la mémoire à l'intérieur de la fonction ou remplir un tampon préalloué fourni par l'appelant.

Option 1:

allouer dynamiquement la mémoire à l'intérieur de la fonction (appelant responsable de la désallocation 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;
}

Appelez ça comme ça:

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

    return 0;
}

Option 2:

remplir un tampon préalloué fourni par l'appelant (l'appelant alloue bufet passe à la fonction)

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

Et appelez-le comme ça:

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.
la source
33
Option 3: (un tableau statique)
moooeeeep
5
@moooeeeep: Oui, j'ai laissé cela de côté pour garder les choses simples, mais oui, vous pouvez renvoyer un pointeur vers des données statiques déclarées à partir de la fonction.
Ed S.
3
@ user1506919: Je préférerais en fait l'option 2 car il est clair qui alloue et désalloue la mémoire, mais je vais ajouter un exemple pour vous.
Ed S.
7
Option 4: retournez une structure contenant un tableau de taille fixe.
Todd Lehman
2
Option 5: retourne une union contenant un tableau de taille fixe.
sqr163
27

Le traitement des tableaux par C est très différent de celui de Java et vous devrez ajuster votre réflexion en conséquence. Les tableaux en C ne sont pas des objets de première classe (c'est-à-dire qu'une expression de tableau ne conserve pas son "tableau" dans la plupart des contextes). En C, une expression de type "tableau à N éléments de T" sera implicitement convertie ("décroissance") en une expression de type "pointeur vers T", sauf lorsque l'expression de tableau est un opérande des opérateurs ou des opérateurs sizeofunaires &, ou si le expression de tableau est une chaîne littérale utilisée pour initialiser un autre tableau dans une déclaration.

Entre autres choses, cela signifie que vous ne pouvez pas passer une expression de tableau à une fonction et la recevoir en tant que type de tableau ; la fonction reçoit en fait un type de pointeur:

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

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

Dans l'appel à foo, l'expression strest convertie du type char [6]à char *, c'est pourquoi le premier paramètre de fooest déclaré à la char *aplace de char a[6]. Dans sizeof str, puisque l'expression de tableau est un opérande de l' sizeofopérateur, elle n'est pas convertie en type pointeur, vous obtenez donc le nombre d'octets dans le tableau (6).

Si vous êtes vraiment intéressé, vous pouvez lire The Development of the C Language de Dennis Ritchie pour comprendre d'où vient ce traitement.

Le résultat est que les fonctions ne peuvent pas renvoyer de types de tableau, ce qui est bien puisque les expressions de tableau ne peuvent pas non plus être la cible d'une affectation.

La méthode la plus sûre consiste pour l'appelant à définir le tableau et à transmettre son adresse et sa taille à la fonction qui est censée y écrire:

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

Une autre méthode consiste pour la fonction à allouer le tableau dynamiquement et à renvoyer le pointeur et la taille:

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

Dans ce cas, l'appelant est responsable de la désallocation du tableau avec la freefonction de bibliothèque.

Notez que dstdans le code ci-dessus se trouve un simple pointeur vers char, pas un pointeur vers un tableau de char. La sémantique du pointeur et du tableau de C est telle que vous pouvez appliquer l'opérateur indice []à une expression de type tableau ou de type pointeur; les deux src[i]et dst[i]accèdera au i'ième élément du tableau (même s'il srcn'a qu'un type de tableau).

Vous pouvez déclarer un pointeur vers un tableau à N éléments Tet faire quelque chose de similaire:

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

Plusieurs inconvénients avec ce qui précède. Tout d'abord, les anciennes versions de C s'attendent SOME_SIZEà être une constante de compilation, ce qui signifie que cette fonction ne fonctionnera qu'avec une seule taille de tableau. Deuxièmement, vous devez déréférencer le pointeur avant d'appliquer l'indice, ce qui encombre le code. Les pointeurs vers des tableaux fonctionnent mieux lorsque vous avez affaire à des tableaux multidimensionnels.

John Bode
la source
2
Votre lien vers « le développement de C » a cassé ... On dirait qu'il devrait nous diriger ici: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso
@Kundor: Ce qui barreçoit est un pointeur, pas un tableau. Dans le contexte d'une déclaration de paramètre de fonction, T a[N]et T a[]sont tous deux traités comme T *a.
John Bode
@JohnBode: Vous avez raison! Pour une raison quelconque, je pensais que des tableaux de taille fixe étaient passés sur la pile. Je me souviens d'une occasion, il y a de nombreuses années, où j'ai découvert que la taille d'un tableau devait être spécifiée dans la signature du paramètre, mais j'ai dû être confus.
Nick Matteo le
@JohnBode, dans la deuxième partie de code, première ligne: le void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)dernier paramètre doit être de size_ttype non char.
Seyfi
11

Je ne dis pas que c'est la meilleure solution ou une solution préférée au problème donné. Cependant, il peut être utile de se rappeler que les fonctions peuvent renvoyer des structures. Bien que les fonctions ne puissent pas retourner de tableaux, les tableaux peuvent être encapsulés dans des structures et la fonction peut renvoyer la structure transportant ainsi le tableau avec elle. Cela fonctionne pour les tableaux de longueur fixe.

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

J'invite à commenter les forces et les faiblesses de cette technique. Je n'ai pas pris la peine de le faire.

Indinfer
la source
1
ne sait pas pourquoi ce n’est pas la réponse acceptée. La question n'était pas de savoir s'il est possible de renvoyer un pointeur vers un tableau.
Frank Puck
La mémoire est-elle allouée CHAR_ARRAY returnedsur le tas? Il ne peut certainement pas le sur la pile (dans le cadre de la pile de returnArray()droite?
Minh Tran
9

Et cette mise en œuvre délicieusement diabolique?

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

principal 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
la source
2
C'est assez diaboliquement délicieux pour éveiller ma curiosité. Pouvez-vous expliquer un peu plus ce que vous avez fait là-haut ou peut-être suggérer une lecture de ce délicieux que vous appelez? Merci d'avance.
Unheilig
1
@Unheilig - Notez qu'il y a des bugs potentiels, c'était juste une preuve de Concept. Cela dit, l'astuce consiste à renvoyer a en structtant que conteneur / objet de tableau. Pensez-y comme un std :: vector C ++. Le préprocesseur étendrait la intversion de ceci à struct intArray { int* contents; int size; };.
pyrospade
1
J'aime l'approche. pro: c'est une solution générique; contra: solution gourmande en mémoire. Pas optimal pour les vecteurs de tailles connues. Quoi qu'il en soit, cela peut être amélioré avec une allocation de taille initiale. Je voudrais certainement ajouter une vérification d'allocation. Très bonne proposition pour commencer :)
urkon
Orienté objet-esk prévenant le mix-mash. Je l'aime.
Jack Giffin
6

Dans votre cas, vous créez un tableau sur la pile et une fois que vous quittez la portée de la fonction, le tableau sera désalloué. À la place, créez un tableau alloué dynamiquement et renvoyez un pointeur vers celui-ci.

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

}
Homme à sens unique
la source
2
Il n'y a pas d' newopérateur en C. C'est C ++.
Eric Postpischil
1
Et sizeof(char)c'est garanti 1, donc dans ce cas, vous pouvez supprimer ce bit malloc.
Ed S.
ok donc si je voulais imprimer le contenu du nouveau tableau, pourrais-je simplement faire mon instruction 'printf' mais remplacer 'returnedArray' par 'arr'?
user1506919
Vous n'appelez pas correctement la fonction (un seul argument lorsque la signature en requiert deux).
Ed S.
Vous passez &arr. Vous voulez arrêtre un char *, et le transmettre en utilisant arr.
chris
4

Vous pouvez le faire en utilisant la mémoire du tas (via l' invocation de malloc () ) comme les autres réponses rapportées ici, mais vous devez toujours gérer la mémoire (utilisez la fonction free () chaque fois que vous appelez votre fonction). Vous pouvez également le faire avec un tableau statique:

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

// do something in your array here

return array; 
}

Vous pouvez ensuite l'utiliser sans vous soucier de la gestion de la mémoire.

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

Dans cet exemple, vous devez utiliser le mot-clé static dans la définition du tableau pour définir la durée de vie du tableau à long terme, afin qu'il ne soit pas détruit après l'instruction return. Bien sûr, de cette manière, vous occupez SIZE octets dans votre mémoire pendant toute la durée de vie de l'application, alors dimensionnons-la correctement!

mengo
la source
2

Votre méthode renverra une variable de pile locale qui échouera gravement. Pour renvoyer un tableau, créez-en un en dehors de la fonction, passez-le par adresse dans la fonction, puis modifiez-le, ou créez un tableau sur le tas et renvoyez cette variable. Les deux fonctionneront, mais le premier ne nécessite aucune allocation de mémoire dynamique pour le faire fonctionner correctement.

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
la source
0

Vous pouvez utiliser un code comme celui-ci:

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

Lorsque vous faites cela, la mémoire doit être libérée ultérieurement, en transmettant l'adresse à free.

Il existe d'autres options. Une routine peut renvoyer un pointeur vers un tableau (ou une partie d'un tableau) qui fait partie d'une structure existante. L'appelant peut passer un tableau et la routine écrit simplement dans le tableau, plutôt que d'allouer de l'espace pour un nouveau tableau.

Eric Postpischil
la source