Retornando uma matriz usando C

152

Eu sou relativamente novo em C e preciso de ajuda com métodos para lidar com matrizes. Vindo da programação Java, estou acostumado a ser capaz de dizer int [] method()para retornar uma matriz. No entanto, descobri que com C você precisa usar ponteiros para matrizes quando as devolve. Sendo um novo programador, eu realmente não entendo nada disso, mesmo com os muitos fóruns que examinei.

Basicamente, estou tentando escrever um método que retorne uma matriz de caracteres em C. Fornecerei o método (vamos chamá-lo de returnArray) com uma matriz. Ele criará uma nova matriz a partir da matriz anterior e retornará um ponteiro para ela. Eu só preciso de alguma ajuda sobre como começar e como ler o ponteiro, uma vez que é enviado para fora da matriz. Qualquer ajuda para explicar isso é apreciada.

Formato de código proposto para a função de retorno de matriz

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

Chamador da função

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

Ainda não testei isso, pois meu compilador C não está funcionando no momento, mas gostaria de descobrir isso

user1506919
fonte
A matriz de retorno é um tamanho conhecido, conforme indicado no seu exemplo de código? A única outra dica que eu vejo além dos problemas de pilha mencionados nas respostas é que, se o seu array de retorno tiver um tamanho indeterminado, dada a maneira como os ponteiros / matrizes funcionam em C, você não saberá o tamanho.
22412
Sim, eu sei o tamanho da matriz de entrada o tempo todo. O tamanho da matriz de entrada e saída não será alterado.
user1506919
1
O desenvolvimento da linguagem C * - bell-labs.com/usr/dmr/www/chist.html
x4444

Respostas:

225

Você não pode retornar matrizes de funções em C. Você também não pode (não deveria) fazer isso:

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 é criado com duração de armazenamento automático e as referências a ele se tornam inválidas quando deixar seu escopo de declaração, ou seja, quando a função retornar.

Você precisará alocar dinamicamente a memória dentro da função ou preencher um buffer pré-alocado fornecido pelo chamador.

Opção 1:

alocar dinamicamente a memória dentro da função (responsável pela desalocação 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;
}

Chame assim:

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

    return 0;
}

Opção 2:

preencher um buffer pré-alocado fornecido pelo chamador (o chamador aloca bufe passa para a função)

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

E chame assim:

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.
fonte
33
Opção 3: (uma matriz estática)
moooeeeep
5
@moooeeeep: Sim, deixei de fora propositadamente para manter as coisas simples, mas sim, você pode retornar um ponteiro para os dados estáticos declarados de dentro da função.
28412 Ed S.
3
@ user1506919: Na verdade, eu preferiria a opção 2, pois é claro quem aloca e desaloca a memória, mas adicionarei um exemplo para você.
28512 Ed Ed
7
Opção 4: retornar uma estrutura que contém uma matriz de tamanho fixo.
Todd Lehman
2
Opção 5: retornar uma união que contenha uma matriz de tamanho fixo.
Sql163 18/05
27

O tratamento de C de matrizes é muito diferente do Java, e você terá que ajustar seu pensamento de acordo. Matrizes em C não são objetos de primeira classe (ou seja, uma expressão de matriz não mantém sua "matriz" na maioria dos contextos). Em C, uma expressão do tipo "N-elemento da matriz de T" será implicitamente convertida ("decaimento") em uma expressão do tipo "ponteiro para T", exceto quando a expressão da matriz for um operando dos sizeofoperadores ou unários &, ou se o expressão de matriz é uma cadeia de caracteres literal usada para inicializar outra matriz em uma declaração.

Entre outras coisas, isso significa que você não pode passar uma expressão de matriz para uma função e recebê-la como um tipo de matriz ; a função realmente recebe um tipo de ponteiro:

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

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

Na chamada para foo, a expressão stré convertida do tipo char [6]para char *, e é por isso que o primeiro parâmetro de fooé declarado em char *avez de char a[6]. Em sizeof str, como a expressão da matriz é um operando do sizeofoperador, ela não é convertida em um tipo de ponteiro; portanto, você obtém o número de bytes na matriz (6).

Se você estiver realmente interessado, pode ler O desenvolvimento da linguagem C, de Dennis Ritchie, para entender de onde vem esse tratamento.

O resultado é que as funções não podem retornar tipos de matriz, o que é bom, pois as expressões de matriz também não podem ser o destino de uma atribuição.

O método mais seguro é o chamador definir a matriz e passar seu endereço e tamanho para a função que deveria escrever para ela:

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

Outro método é a função alocar a matriz dinamicamente e retornar o ponteiro e o tamanho:

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

Nesse caso, o chamador é responsável por desalocar a matriz com a freefunção de biblioteca.

Observe que dstno código acima é um ponteiro simples para char, não um ponteiro para uma matriz de char. A semântica de ponteiro e matriz de C é tal que você pode aplicar o operador subscrito []a uma expressão do tipo de matriz ou tipo de ponteiro; ambos src[i]e dst[i]acessarão o i'ésimo elemento da matriz (mesmo que apenas srctenha um tipo de matriz).

Você pode declarar um ponteiro para uma matriz de elementos N Te fazer algo semelhante:

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

Várias desvantagens com o acima exposto. Antes de tudo, as versões mais antigas do C esperam SOME_SIZEser uma constante em tempo de compilação, o que significa que a função funcionará apenas com um tamanho de matriz. Em segundo lugar, é necessário desreferenciar o ponteiro antes de aplicar o subscrito, o que atrapalha o código. Ponteiros para matrizes funcionam melhor quando você está lidando com matrizes multidimensionais.

John Bode
fonte
2
Sua ligação com "o desenvolvimento de C" tem quebrado ... parece que ele deve dirigir-nos aqui: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso
@ Kundor: O que barrecebe é um ponteiro, não uma matriz. No contexto de uma declaração de parâmetro de função T a[N]e T a[]são tratados como T *a.
John Bode
@JohnBode: Você está certo! Por alguma razão, pensei que matrizes de tamanho fixo fossem passadas na pilha. Lembro-me de uma ocasião, há muitos anos, em que descobri que o tamanho de uma matriz precisava ser especificado na assinatura do parâmetro, mas devo ter ficado confuso.
Nick Matteo
@JohnBode, na segunda parte do código primeira linha: o void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)último parâmetro deve estar no size_ttipo não char.
Seyfi 17/11/19
11

Não estou dizendo que esta é a melhor solução ou a solução preferida para o problema em questão. No entanto, pode ser útil lembrar que as funções podem retornar estruturas. Embora as funções não possam retornar matrizes, elas podem ser agrupadas em estruturas e a função pode retornar a estrutura, transportando a matriz com ela. Isso funciona para matrizes de comprimento fixo.

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

Convido comentários sobre os pontos fortes e fracos desta técnica. Eu não me incomodei em fazê-lo.

Indinfer
fonte
1
não está claro por que essa não é a resposta aceita. A questão não era se é possível retornar um ponteiro para uma matriz.
Frank Puck
A memória está alocada para CHAR_ARRAY returnedo heap? Certamente não pode na pilha (no quadro de pilha da returnArray()direita?
Minh Tran
9

Que tal essa implementação deliciosamente má?

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;
}
pyrospade
fonte
2
Isso é diabolicamente delicioso o suficiente para despertar minha curiosidade. Você pode explicar um pouco mais o que você fez lá em cima ou talvez sugerir uma leitura dessa delícia que você chama? Desde já, obrigado.
Unheilig
1
@ Unheilig - Note que existem sim bugs em potencial nisso, era apenas uma prova do conceito. Dito isso, o truque é retornar a structcomo um contêiner / objeto de matriz. Pense nisso como um C ++ std :: vector. O pré-processador expandiria a intversão disso para struct intArray { int* contents; int size; };.
Pyrospade
1
Eu gosto da abordagem. pro: esta é uma solução genérica; contra: solução intensiva de memória. Não é ideal para vetores de tamanhos conhecidos. De qualquer forma, isso pode ser atualizado com a alocação de tamanho inicial. Eu definitivamente acrescentaria alguma verificação de alocação. Muito boa proposta para começar :)
urkon
Esk orientada a objeto que possui mix-mash. Eu gosto disso.
Jack Giffin
6

No seu caso, você está criando uma matriz na pilha e depois de deixar o escopo da função, a matriz será desalocada. Em vez disso, crie uma matriz alocada dinamicamente e retorne um ponteiro para ela.

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

}
Homem de sentido único
fonte
2
Não há newoperador em C. Isso é C ++.
Eric Postpischil
1
E sizeof(char)é garantido que é 1, portanto, neste caso, você pode largar esse pedaço malloc.
Ed S.
ok Então, se eu quisesse imprimir o conteúdo da nova matriz, eu poderia apenas fazer a minha declaração 'printf', mas substituir 'returnArray' por 'arr'?
user1506919
Você não está chamando a função corretamente (apenas um argumento quando a assinatura requer dois).
Ed S.
Você está passando &arr. Você quer arrser um char *e passá-lo usando arr.
28412 chris
4

Você pode fazer isso usando a memória heap (através da chamada malloc () ) como outras respostas relatadas aqui, mas sempre deve gerenciar a memória (use a função free () sempre que chamar sua função). Você também pode fazer isso com uma matriz estática:

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

// do something in your array here

return array; 
}

Você pode usá-lo sem se preocupar com o gerenciamento de memória.

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

Neste exemplo, você deve usar a palavra-chave estática na definição de matriz para definir a duração da vida útil da matriz para o aplicativo, para que não seja destruída após a declaração de retorno. Obviamente, dessa maneira, você ocupa SIZE bytes em sua memória por toda a vida útil do aplicativo, então dimensione-o corretamente!

mengo
fonte
2

Seu método retornará uma variável de pilha local que falhará mal. Para retornar uma matriz, crie uma fora da função, passe-a por endereço para a função, modifique-a ou crie uma matriz na pilha e retorne essa variável. Ambos funcionarão, mas o primeiro não requer nenhuma alocação de memória dinâmica para fazê-lo funcionar corretamente.

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

Você pode usar código como este:

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

Quando você faz isso, a memória deve ser liberada posteriormente, passando o endereço para liberar.

Existem outras opções. Uma rotina pode retornar um ponteiro para uma matriz (ou parte de uma matriz) que faz parte de alguma estrutura existente. O chamador pode passar uma matriz e a rotina simplesmente grava na matriz, em vez de alocar espaço para uma nova matriz.

Eric Postpischil
fonte