กลับอาร์เรย์โดยใช้ C


153

ฉันค่อนข้างใหม่กับ C และฉันต้องการความช่วยเหลือเกี่ยวกับวิธีการจัดการกับอาร์เรย์ มาจากการเขียนโปรแกรม Java ฉันคุ้นเคยกับความสามารถในการพูดint [] method()เพื่อส่งกลับอาร์เรย์ อย่างไรก็ตามฉันพบว่าด้วย C คุณต้องใช้พอยน์เตอร์สำหรับอาร์เรย์เมื่อคุณส่งคืน ในฐานะโปรแกรมเมอร์คนใหม่ฉันไม่เข้าใจเลยแม้แต่เรื่องที่ฉันได้ดูมา

โดยทั่วไปฉันพยายามเขียนวิธีที่ส่งกลับอาร์เรย์ char ใน C. ฉันจะให้วิธีการ (ให้เรียกมันว่า returnArray) กับอาร์เรย์ มันจะสร้างอาร์เรย์ใหม่จากอาร์เรย์ก่อนหน้าและส่งกลับตัวชี้ไปยังมัน ฉันแค่ต้องการความช่วยเหลือเกี่ยวกับวิธีการเริ่มต้นนี้และวิธีการอ่านตัวชี้เมื่อมันถูกส่งออกจากอาร์เรย์ ความช่วยเหลือใด ๆ ที่อธิบายสิ่งนี้ยินดี

รูปแบบรหัสที่เสนอสำหรับฟังก์ชันส่งคืนอาร์เรย์

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

ผู้เรียกฟังก์ชั่น

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

ฉันยังไม่ได้ทดสอบสิ่งนี้เนื่องจากคอมไพเลอร์ C ของฉันไม่ทำงานในขณะนี้ แต่ฉันต้องการจะเข้าใจสิ่งนี้


อาร์เรย์ส่งคืนเป็นขนาดที่รู้จักตามที่ระบุในตัวอย่างรหัสของคุณหรือไม่ gotcha อื่น ๆ ที่ฉันเห็นนอกเหนือจากปัญหาสแต็คที่กล่าวถึงในคำตอบก็คือถ้าอาร์เรย์ส่งคืนของคุณมีขนาดไม่แน่นอนเนื่องจากวิธีที่พอยน์เตอร์ / อาร์เรย์ทำงานใน C คุณจะไม่ทราบว่ามันใหญ่แค่ไหน
strangefreeworld

ใช่ฉันรู้ขนาดของอาร์เรย์ที่ใช้งานได้ตลอดเวลา ขนาดของอินพุตและเอาต์พุตอาร์เรย์จะไม่เปลี่ยนแปลง
user1506919

1
การพัฒนาภาษา C * - bell-labs.com/usr/dmr/www/chist.html
x4444

คำตอบ:


225

คุณไม่สามารถส่งคืนอาร์เรย์จากฟังก์ชันใน 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 ถูกสร้างขึ้นด้วยระยะเวลาการเก็บข้อมูลอัตโนมัติและการอ้างอิงถึงจะไม่ถูกต้องเมื่อออกจากขอบเขตการประกาศคือเมื่อฟังก์ชันส่งคืน

คุณจะต้องจัดสรรหน่วยความจำภายในของฟังก์ชั่นแบบไดนามิกหรือเติมบัฟเฟอร์ที่จัดสรรล่วงหน้าให้โดยผู้โทร

ตัวเลือกที่ 1:

จัดสรรหน่วยความจำภายในฟังก์ชั่นแบบไดนามิก (ผู้เรียกรับผิดชอบการจัดสรรคืน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;
}

เรียกว่าเป็นเช่นนั้น:

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

    return 0;
}

ตัวเลือก 2:

เติมบัฟเฟอร์ที่จัดสรรล่วงหน้าให้โดยผู้เรียก (ผู้โทรจัดสรรbufและส่งผ่านไปยังฟังก์ชั่น)

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

และเรียกมันว่าอย่างนั้น:

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

33
ตัวเลือก 3: (อาร์เรย์คงที่)
moooeeeep

5
@moooeeeep: ใช่ฉันออกไปโดยมีจุดประสงค์เพื่อให้สิ่งที่เรียบง่าย แต่ใช่คุณสามารถกลับตัวชี้ไปยังข้อมูลคงที่ประกาศจากภายในฟังก์ชั่น
Ed S.

3
@ user1506919: จริง ๆ แล้วฉันต้องการตัวเลือกที่ 2 เนื่องจากเป็นที่ชัดเจนว่าใครเป็นผู้จัดสรรและยกเลิกการจัดสรรหน่วยความจำ แต่ฉันจะเพิ่มตัวอย่างให้คุณ
Ed S.

7
ตัวเลือก 4: ส่งคืนโครงสร้างที่มีอาร์เรย์ขนาดคงที่
ทอดด์เลห์แมน

2
ตัวเลือกที่ 5: ส่งคืนการรวมที่มีอาร์เรย์ขนาดคงที่
sqr163

27

การรักษาอาร์เรย์ของ C นั้นแตกต่างจาก Java อย่างมากและคุณจะต้องปรับความคิดของคุณให้เหมาะสม อาร์เรย์ใน C ไม่ใช่วัตถุชั้นหนึ่ง (นั่นคือนิพจน์อาร์เรย์ไม่ได้เก็บรักษาว่าเป็น "array-ness" ในบริบทส่วนใหญ่) ใน C การแสดงออกของประเภท "อาร์เรย์ N องค์ประกอบของT" จะถูกแปลงโดยปริยาย ("การสลายตัว") เพื่อการแสดงออกของประเภท "ตัวชี้ไปที่T" ยกเว้นเมื่อการแสดงออกอาร์เรย์เป็นตัวถูกดำเนินการของsizeofหรือ&ผู้ประกอบการเอกหรือ การแสดงออกของอาร์เรย์เป็นตัวอักษรสตริงที่ใช้ในการเริ่มต้นอาร์เรย์อื่นในการประกาศ

เหนือสิ่งอื่นใดที่นี้หมายถึงว่าคุณไม่สามารถผ่านการแสดงออกอาร์เรย์ฟังก์ชั่นและมีมันได้รับเป็นประเภทอาร์เรย์ ; ฟังก์ชั่นจริงได้รับประเภทตัวชี้:

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

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

ในการเรียกร้องให้fooแสดงออกstrถูกแปลงจากประเภทchar [6]การchar *ซึ่งเป็นเหตุผลที่พารามิเตอร์แรกของfooการประกาศแทนchar *a char a[6]ในsizeof strเนื่องจากนิพจน์อาร์เรย์เป็นตัวถูกดำเนินการของตัวsizeofดำเนินการจึงไม่ถูกแปลงเป็นชนิดตัวชี้ดังนั้นคุณจะได้รับจำนวนไบต์ในอาร์เรย์ (6)

หากคุณสนใจจริงๆคุณสามารถอ่าน Dennis Ritchie's The Development of the C Languageเพื่อทำความเข้าใจว่าการรักษานี้มาจากไหน

ผลที่สุดคือฟังก์ชั่นไม่สามารถคืนค่าประเภทอาเรย์ซึ่งใช้ได้เนื่องจากการแสดงออกของอาเรย์ไม่สามารถเป็นเป้าหมายของการกำหนดได้เช่นกัน

วิธีที่ปลอดภัยที่สุดสำหรับผู้โทรเพื่อกำหนดอาร์เรย์และส่งที่อยู่และขนาดไปยังฟังก์ชันที่ควรจะเขียน:

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

อีกวิธีคือฟังก์ชั่นในการจัดสรรอาเรย์แบบไดนามิกและส่งกลับตัวชี้และขนาด:

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

ในกรณีนี้ผู้เรียกมีหน้าที่ยกเลิกการจัดสรรอาร์เรย์ด้วยfreeฟังก์ชันไลบรารี

โปรดทราบว่าdstในโค้ดข้างต้นเป็นตัวชี้ง่ายที่จะไม่ได้เป็นตัวชี้ไปยังอาร์เรย์ของchar charตัวชี้และซีแมนทิกส์อาร์เรย์ของ C เป็นเช่นนั้นซึ่งคุณสามารถใช้ตัวดำเนินการตัวห้อย[]กับนิพจน์ของชนิดอาร์เรย์หรือชนิดตัวชี้ ทั้งสองsrc[i]และdst[i]จะเข้าถึงiองค์ประกอบ 'ของอาร์เรย์ (แม้ว่าจะsrcมีเฉพาะประเภทอาร์เรย์)

คุณสามารถประกาศตัวชี้ไปยังอาร์เรย์ขององค์ประกอบ N Tและทำสิ่งที่คล้ายกัน:

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

ข้อเสียหลายประการข้างต้น ประการแรกรุ่นเก่าของ C คาดว่าSOME_SIZEจะมีค่าคงที่เวลารวบรวมซึ่งหมายความว่าฟังก์ชั่นจะทำงานกับขนาดอาร์เรย์ได้เพียงครั้งเดียว ประการที่สองคุณจะต้องตรวจสอบตัวชี้อีกครั้งก่อนที่จะใช้ตัวห้อยซึ่งจะตัดโค้ด ตัวชี้ไปยังอาร์เรย์ทำงานได้ดีขึ้นเมื่อคุณจัดการกับอาร์เรย์หลายมิติ


2
ลิงก์ของคุณไปยัง "การพัฒนาของ C" ใช้งานไม่ได้ ... ดูเหมือนว่าควรนำเรามาที่นี่: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso

@Kundor: สิ่งที่barได้รับคือตัวชี้ไม่ใช่อาร์เรย์ ในบริบทของการประกาศพารามิเตอร์ฟังก์ชั่นT a[N]และมีทั้งที่ถือว่าเป็นT a[] T *a
John Bode

@JohnBode: ถูกต้อง! ด้วยเหตุผลบางอย่างฉันคิดว่าอาร์เรย์ขนาดคงที่ถูกส่งผ่านไปยังสแต็ก ฉันจำเหตุการณ์เมื่อหลายปีก่อนเมื่อฉันพบว่าขนาดของอาร์เรย์ต้องถูกระบุไว้ในลายเซ็นของพารามิเตอร์ แต่ฉันต้องสับสน
Nick Matteo

@JohnBode ในบรรทัดแรกรหัสสองส่วน: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)พารามิเตอร์ที่ผ่านมาควรจะอยู่ในประเภทไม่ได้size_t char
Seyfi

11

ฉันไม่ได้บอกว่านี่เป็นทางออกที่ดีที่สุดหรือเป็นทางออกที่ต้องการสำหรับปัญหาที่กำหนด อย่างไรก็ตามมันอาจมีประโยชน์ในการจำว่าฟังก์ชั่นสามารถคืน struct แม้ว่าฟังก์ชั่นจะไม่สามารถคืนค่าอาร์เรย์ได้ แต่อาร์เรย์สามารถห่อหุ้มได้ในโครงสร้างและฟังก์ชันสามารถคืนค่าโครงสร้างได้ดังนั้นจึงต้องใช้อาร์เรย์ด้วย ใช้งานได้กับอาร์เรย์ความยาวคงที่

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

ฉันเชิญความคิดเห็นเกี่ยวกับจุดแข็งและจุดอ่อนของเทคนิคนี้ ฉันไม่ได้ใส่ใจที่จะทำเช่นนั้น


1
ไม่ชัดเจนว่าทำไมถึงไม่เป็นคำตอบที่ยอมรับได้ คำถามไม่ได้หากเป็นไปได้ที่จะคืนค่าตัวชี้ไปยังอาร์เรย์
Frank Puck

มีการจัดสรรหน่วยความจำสำหรับCHAR_ARRAY returnedฮีปหรือไม่ แน่นอนมันไม่ได้เกี่ยวกับสแต็ค (ในกรอบสแต็คของreturnArray()ใช่มั้ย?
มินห์ Tran

9

วิธีการเกี่ยวกับการใช้ความชั่วร้ายโอชะนี้?

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

2
มันอร่อยมากพอที่จะทำให้ฉันอยากรู้อยากเห็น คุณช่วยอธิบายอีกเล็กน้อยว่าคุณทำอะไรที่นั่นหรืออาจจะแนะนำให้อ่านความอร่อยที่คุณเรียก? ขอบคุณล่วงหน้า.
Unheilig

1
@ Unheilig - โปรดทราบว่ามีข้อบกพร่องที่อาจเกิดขึ้นในเรื่องนี้มันเป็นเพียงการพิสูจน์แนวคิด ที่กล่าวว่าเคล็ดลับจะส่งกลับstructเป็นภาชนะอาร์เรย์ / วัตถุ คิดว่ามันเหมือน C ++ std :: vector พรีโพรเซสเซอร์จะขยายรุ่นนี้int struct intArray { int* contents; int size; };
pyrospade

1
ฉันชอบวิธีการ พ่อแม่: นี่เป็นคำตอบทั่วไป; Contra: หน่วยความจำแบบเข้มข้น ไม่เหมาะสำหรับเวกเตอร์ที่มีขนาด kown อย่างไรก็ตามสิ่งนี้สามารถอัพเกรดได้ด้วยการจัดสรรขนาดเริ่มต้น ฉันจะเพิ่มการตรวจสอบการจัดสรรอย่างแน่นอน ข้อเสนอที่ดีมากที่จะเริ่มต้นด้วย :)
urkon

Object-Esk ที่เน้นการผสมส่วนผสม ฉันชอบมัน.
Jack Giffin

6

ในกรณีของคุณคุณกำลังสร้างอาร์เรย์บนสแต็กและเมื่อคุณออกจากขอบเขตฟังก์ชันแล้วอาร์เรย์จะถูกจัดสรรคืน สร้างอาร์เรย์ที่จัดสรรแบบไดนามิกแทนและส่งกลับตัวชี้ไปยังอาร์เรย์นั้น

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

}

2
ไม่มีnewโอเปอเรเตอร์ใน C นั่นคือ C ++
Eric Postpischil

1
และsizeof(char)รับประกันว่าจะเป็น1ดังนั้นในกรณีนี้คุณสามารถปล่อยบิตmallocนั้นได้
Ed S.

ตกลงดังนั้นหากฉันต้องการพิมพ์เนื้อหาของอาร์เรย์ใหม่ฉันสามารถทำคำสั่ง 'printf' ของฉัน แต่แทนที่ 'returnArray' ด้วย 'arr' หรือไม่
user1506919

คุณเรียกใช้ฟังก์ชันไม่ถูกต้อง (มีเพียงอาร์กิวเมนต์เดียวเท่านั้นที่ต้องใช้ลายเซ็นสองรายการ)
Ed S.

&arrคุณผ่านใน คุณต้องการที่arrจะเป็นและผ่านมันในการใช้char * arr
chris

4

คุณสามารถทำได้โดยใช้หน่วยความจำฮีป (ผ่านการเรียกใช้malloc () ) เหมือนกับคำตอบอื่น ๆ ที่ได้รับการรายงานที่นี่ แต่คุณต้องจัดการหน่วยความจำ (ใช้ฟังก์ชั่นfree ()ทุกครั้งที่คุณเรียกใช้ฟังก์ชันของคุณ) คุณสามารถทำได้ด้วยอาร์เรย์คงที่:

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

// do something in your array here

return array; 
}

คุณสามารถใช้งานได้โดยไม่ต้องกังวลเกี่ยวกับการจัดการหน่วยความจำ

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

ในตัวอย่างนี้คุณต้องใช้คำสำคัญคงที่ในการกำหนดอาเรย์เพื่อตั้งค่าให้อายุการใช้งานของอาเรย์นานขึ้นดังนั้นมันจะไม่ถูกทำลายหลังจากคำสั่งส่งคืน แน่นอนว่าด้วยวิธีนี้คุณใช้ SIZE ไบต์ในหน่วยความจำตลอดอายุการใช้งานของแอปพลิเคชันดังนั้นปรับขนาดให้เหมาะสม!


2

วิธีการของคุณจะส่งกลับตัวแปรสแต็กท้องถิ่นที่จะล้มเหลวไม่ดี ในการส่งคืนอาเรย์ให้สร้างหนึ่งนอกฟังก์ชั่นส่งผ่านโดยที่อยู่ในฟังก์ชั่นจากนั้นแก้ไขหรือสร้างอาเรย์บนฮีปและส่งกลับตัวแปรนั้น ทั้งสองอย่างจะทำงานได้ แต่สิ่งแรกไม่ต้องการการจัดสรรหน่วยความจำแบบไดนามิกเพื่อให้ทำงานได้อย่างถูกต้อง

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

0

คุณสามารถใช้รหัสเช่นนี้:

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

เมื่อคุณทำเช่นนี้หน่วยความจำที่ควรจะเป็นอิสระในภายหลังโดยผ่านที่อยู่เพื่อปลดปล่อย

มีตัวเลือกอื่น ๆ ชุดคำสั่งอาจส่งคืนตัวชี้ไปยังอาร์เรย์ (หรือส่วนของอาร์เรย์) ที่เป็นส่วนหนึ่งของโครงสร้างที่มีอยู่ ผู้เรียกอาจผ่านอาร์เรย์และรูทีนเขียนลงในอาร์เรย์แทนที่จะจัดสรรพื้นที่สำหรับอาร์เรย์ใหม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.