Mengembalikan array menggunakan C

153

Saya relatif baru untuk C dan saya butuh bantuan dengan metode berurusan dengan array. Berasal dari pemrograman Java, saya terbiasa bisa mengatakan int [] method()untuk mengembalikan array. Namun, saya telah menemukan bahwa dengan C Anda harus menggunakan pointer untuk array ketika Anda mengembalikannya. Menjadi seorang programmer baru, saya benar-benar tidak mengerti ini sama sekali, bahkan dengan banyak forum yang telah saya pelajari.

Pada dasarnya, saya mencoba menulis metode yang mengembalikan array char di C. Saya akan menyediakan metode (sebut saja returnArray) dengan sebuah array. Ini akan membuat array baru dari array sebelumnya dan mengembalikan pointer ke sana. Saya hanya butuh bantuan tentang cara memulai ini dan cara membaca pointer setelah dikirim keluar dari array. Setiap bantuan yang menjelaskan hal ini sangat dihargai.

Format Kode yang Diusulkan untuk Fungsi Pengembalian Array

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

Pemanggil Fungsi

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

Saya belum menguji ini karena kompiler C saya tidak berfungsi saat ini tetapi saya ingin mencari tahu ini

pengguna1506919
sumber
Apakah array kembali ukuran yang dikenal seperti yang ditunjukkan dalam sampel kode Anda? Satu-satunya gotcha lain yang saya lihat selain masalah stack yang disebutkan dalam jawaban adalah bahwa jika array pengembalian Anda adalah ukuran yang tidak ditentukan, mengingat cara pointer / array bekerja di C, Anda tidak akan tahu seberapa besar itu.
oddfreeworld
Ya, saya tahu ukuran array incomming setiap saat. Ukuran input dan output array tidak akan berubah.
user1506919
1
Perkembangan Bahasa C * - bell-labs.com/usr/dmr/www/chist.html
x4444

Jawaban:

225

Anda tidak dapat mengembalikan array dari fungsi di C. Anda juga tidak dapat (tidak seharusnya) melakukan ini:

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 dibuat dengan durasi penyimpanan otomatis dan referensi untuknya akan menjadi tidak valid begitu ia meninggalkan ruang lingkup yang menyatakan, yaitu, ketika fungsi kembali.

Anda perlu secara dinamis mengalokasikan memori di dalam fungsi atau mengisi buffer yang telah dialokasikan sebelumnya yang disediakan oleh pemanggil.

Pilihan 1:

mengalokasikan memori di dalam fungsi secara dinamis (penelepon yang bertanggung jawab untuk melakukan deallocating 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;
}

Sebut seperti ini:

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

    return 0;
}

Pilihan 2:

mengisi buffer yang telah dialokasikan sebelumnya yang disediakan oleh pemanggil (pemanggil mengalokasikan bufdan meneruskan ke fungsi)

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

Dan menyebutnya seperti ini:

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.
sumber
33
Opsi 3: (array statis)
moooeeeep
5
@ooooeeeep: Ya, saya sengaja mengabaikannya, tetapi ya, Anda bisa mengembalikan pointer ke data statis yang dideklarasikan dari dalam fungsi.
Ed S.
3
@ user1506919: Saya sebenarnya lebih suka opsi 2 karena jelas siapa yang mengalokasikan dan membatalkan alokasi memori, tapi saya akan menambahkan contoh untuk Anda.
Ed S.
7
Opsi 4: Kembalikan struct yang berisi array ukuran tetap.
Todd Lehman
2
Opsi 5: Kembalikan penyatuan yang berisi larik ukuran tetap.
sqr163
27

Perlakuan C pada array sangat berbeda dari Java, dan Anda harus menyesuaikan pemikiran Anda. Array dalam C bukan objek kelas satu (yaitu, ekspresi array tidak mempertahankannya "array-ness" dalam sebagian besar konteks). Dalam C, ekspresi tipe "N-element array T" akan secara implisit dikonversi ("decay") menjadi ekspresi tipe "pointer to T", kecuali ketika ekspresi array adalah operan dari operator sizeofatau unary &, atau jika ekspresi array adalah string literal yang digunakan untuk menginisialisasi array lain dalam deklarasi.

Di antara hal-hal lain, ini berarti bahwa Anda tidak dapat meneruskan ekspresi array ke suatu fungsi dan menerimanya sebagai tipe array ; fungsi sebenarnya menerima tipe pointer:

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

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

Dalam panggilan ke foo, ekspresi strdikonversi dari tipe char [6]ke char *, itulah sebabnya parameter pertama foodideklarasikan char *adaripada char a[6]. Dalam sizeof str, karena ekspresi array adalah operan dari sizeofoperator, itu tidak dikonversi ke tipe pointer, sehingga Anda mendapatkan jumlah byte dalam array (6).

Jika Anda benar - benar tertarik, Anda dapat membaca buku The Development of the C Dennis Ritchie untuk memahami dari mana perawatan ini berasal.

Hasilnya adalah bahwa fungsi tidak dapat mengembalikan tipe array, yang baik karena ekspresi array tidak dapat menjadi target penugasan, baik.

Metode teraman bagi penelepon untuk menentukan array, dan meneruskan alamat dan ukurannya ke fungsi yang seharusnya ditulis untuk itu:

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

Metode lain adalah untuk fungsi untuk mengalokasikan array secara dinamis dan mengembalikan pointer dan ukuran:

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

Dalam hal ini, pemanggil bertanggung jawab untuk membatalkan alokasi array dengan freefungsi perpustakaan.

Perhatikan bahwa dstdalam kode di atas adalah penunjuk sederhana char, bukan penunjuk ke array char. Pointer dan semantik array C sedemikian rupa sehingga Anda dapat menerapkan operator subskrip []ke ekspresi tipe array atau tipe pointer; keduanya src[i]dan dst[i]akan mengakses ielemen larik array (walaupun hanya srcmemiliki tipe larik).

Anda dapat mendeklarasikan pointer ke array elemen-N Tdan melakukan sesuatu yang serupa:

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

Beberapa kekurangan dengan di atas. Pertama-tama, versi C yang lebih lama diharapkan SOME_SIZEsebagai konstanta waktu kompilasi, artinya fungsi hanya akan bekerja dengan satu ukuran array. Kedua, Anda harus melakukan dereferensi pointer sebelum menerapkan subskrip, yang mengacaukan kode. Pointer ke array bekerja lebih baik ketika Anda berurusan dengan array multi-dimensi.

John Bode
sumber
2
Tautan Anda ke "pengembangan C" telah rusak ... sepertinya harus mengarahkan kami di sini: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso
@Kundor: Yang barmenerima adalah pointer, bukan array. Dalam konteks deklarasi parameter fungsi, T a[N]dan T a[]keduanya diperlakukan sebagai T *a.
John Bode
@JohnBode: Anda benar! Untuk beberapa alasan saya pikir array ukuran tetap dilewatkan pada stack. Saya ingat suatu kesempatan, bertahun-tahun yang lalu, ketika saya menemukan bahwa ukuran array harus ditentukan dalam tanda tangan parameter, tetapi saya pasti bingung.
Nick Matteo
@JohnBode, di bagian kode kedua baris pertama: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)parameter terakhir harus dalam size_ttipe tidak char.
Seyfi
11

Saya tidak mengatakan bahwa ini adalah solusi terbaik atau solusi pilihan untuk masalah yang diberikan. Namun, mungkin berguna untuk mengingat bahwa fungsi dapat mengembalikan struct. Meskipun fungsi tidak dapat mengembalikan array, array dapat dibungkus dengan struct dan fungsi dapat mengembalikan struct sehingga membawa array dengannya. Ini berfungsi untuk array panjang tetap.

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

Saya mengundang komentar tentang kekuatan dan kelemahan teknik ini. Saya tidak repot-repot melakukannya.

Indinfer
sumber
1
tidak jelas mengapa ini bukan jawaban yang diterima. Pertanyaannya bukan apakah mungkin untuk mengembalikan pointer ke array.
Frank Puck
Apakah memori dialokasikan untuk CHAR_ARRAY returneddi heap? Itu tentu tidak bisa di stack (dalam bingkai stack returnArray()kan?
Minh Tran
9

Bagaimana dengan implementasi kejahatan yang nikmat ini?

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
sumber
2
Enak sekali, ini cukup lezat untuk membangkitkan rasa ingin tahu saya. Bisakah Anda menjelaskan sedikit lebih banyak tentang apa yang Anda lakukan di sana atau mungkin menyarankan agar Anda membaca kelezatan yang Anda sebut ini? Terima kasih sebelumnya.
Unheilig
1
@Unheilig - Perhatikan bahwa ada bug potensial sime dalam hal ini, itu hanya bukti Konsep. Yang mengatakan, triknya adalah mengembalikan structwadah / objek array. Anggap saja seperti vektor C ++ std ::. Preprocessor akan memperluas intversi ini ke struct intArray { int* contents; int size; };.
pyrospade
1
Saya suka pendekatannya. pro: ini adalah solusi umum; kontra: solusi intensif memori. Tidak optimal untuk vektor ukuran kown. Bagaimanapun ini dapat ditingkatkan dengan alokasi ukuran inital. Saya akan menambahkan definitley cek alokasi. Usul yang sangat bagus untuk memulai :)
urkon
Object-esk prepossessing mix-mash. Saya suka itu.
Jack Giffin
6

Dalam kasus Anda, Anda membuat array di stack dan setelah Anda meninggalkan ruang lingkup fungsi, array akan di-deallocated. Sebagai gantinya, buat array yang dialokasikan secara dinamis dan kembalikan pointer ke sana.

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

}
Man of One Way
sumber
2
Tidak ada newoperator di C. Itu adalah C ++.
Eric Postpischil
1
Dan sizeof(char)dijamin 1, jadi dalam hal ini Anda dapat menjatuhkan bit itu dari malloc.
Ed S.
ok jadi Jika saya ingin mencetak isi dari array baru, dapatkah saya melakukan pernyataan 'printf' saya tapi ganti 'returnsArray' dengan 'arr'?
user1506919
Anda tidak memanggil fungsi dengan benar (hanya satu argumen ketika tanda tangan membutuhkan dua).
Ed S.
Anda sedang lewat &arr. Anda ingin arrmenjadi char *, dan meneruskannya dengan menggunakan arr.
chris
4

Anda dapat melakukannya menggunakan memori tumpukan (melalui permintaan malloc () ) seperti jawaban lain yang dilaporkan di sini, tetapi Anda harus selalu mengelola memori (menggunakan fungsi bebas () setiap kali Anda memanggil fungsi Anda). Anda juga dapat melakukannya dengan array statis:

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

// do something in your array here

return array; 
}

Anda dapat menggunakannya tanpa khawatir tentang manajemen memori.

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

Dalam contoh ini Anda harus menggunakan kata kunci statis dalam definisi array untuk mengatur ke aplikasi-panjang array seumur hidup, sehingga tidak akan hancur setelah pernyataan kembali. Tentu saja, dengan cara ini Anda menempati byte SIZE dalam memori Anda untuk seluruh umur aplikasi, jadi ukuranlah dengan benar!

mengo
sumber
2

Metode Anda akan mengembalikan variabel tumpukan lokal yang akan gagal parah. Untuk mengembalikan array, buat satu di luar fungsi, kirimkan dengan alamat ke fungsi, lalu modifikasi, atau buat array di heap dan kembalikan variabel itu. Keduanya akan berfungsi, tetapi yang pertama tidak memerlukan alokasi memori dinamis untuk membuatnya bekerja dengan benar.

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

Anda dapat menggunakan kode seperti ini:

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

Ketika Anda melakukan ini, memori nanti harus dibebaskan, dengan mengirimkan alamat tersebut menjadi bebas.

Ada opsi lain. Rutin mungkin mengembalikan pointer ke array (atau bagian dari array) yang merupakan bagian dari beberapa struktur yang ada. Penelepon mungkin melewati array, dan rutinitas hanya menulis ke dalam array, daripada mengalokasikan ruang untuk array baru.

Eric Postpischil
sumber