Trả về một mảng bằng C


152

Tôi còn khá mới với C và tôi cần một số trợ giúp về các phương thức xử lý mảng. Đến từ lập trình Java, tôi quen với việc có thể nói int [] method()để trả về một mảng. Tuy nhiên, tôi đã phát hiện ra rằng với C, bạn phải sử dụng các con trỏ cho các mảng khi bạn trả lại chúng. Là một lập trình viên mới, tôi thực sự không hiểu điều này chút nào, ngay cả với nhiều diễn đàn tôi đã xem qua.

Về cơ bản, tôi đang cố gắng viết một phương thức trả về một mảng char trong C. Tôi sẽ cung cấp phương thức (hãy gọi nó là returnArray) với một mảng. Nó sẽ tạo một mảng mới từ mảng trước đó và trả về một con trỏ tới nó. Tôi chỉ cần một số trợ giúp về cách bắt đầu điều này và cách đọc con trỏ một khi nó được gửi ra khỏi mảng. Bất kỳ trợ giúp giải thích điều này được đánh giá cao.

Định dạng mã đề xuất cho chức năng trả về mảng

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

Người gọi hàm

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

Tôi chưa thử nghiệm điều này vì trình biên dịch C của tôi không hoạt động vào lúc này nhưng tôi muốn tìm hiểu điều này


Là mảng trả về có kích thước đã biết như được chỉ định trong mẫu mã của bạn? Điều duy nhất mà tôi thấy ngoài các vấn đề ngăn xếp được đề cập trong câu trả lời là nếu mảng trả về của bạn có kích thước không xác định, do cách con trỏ / mảng hoạt động trong C, bạn sẽ không biết nó lớn như thế nào.
strangefreeworld

Vâng, tôi biết kích thước của mảng incomming mọi lúc. Kích thước của mảng đầu vào và đầu ra sẽ không thay đổi.
user1506919

1
Sự phát triển của ngôn ngữ C * - bell-labs.com/usr/dmr/www/chist.html
x4444

Câu trả lời:


225

Bạn không thể trả về mảng từ các hàm trong C. Bạn cũng không thể (không nên) làm điều này:

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 được tạo với thời lượng lưu trữ tự động và các tham chiếu đến nó sẽ trở nên không hợp lệ khi nó rời khỏi phạm vi khai báo của nó, tức là khi hàm trả về.

Bạn sẽ cần phải phân bổ động bộ nhớ bên trong hàm hoặc điền vào bộ đệm được cung cấp bởi người gọi.

Lựa chọn 1:

tự động phân bổ bộ nhớ bên trong hàm (người gọi chịu trách nhiệm giải quyết 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;
}

Gọi nó như vậy:

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

    return 0;
}

Lựa chọn 2:

điền vào một bộ đệm preallocated được cung cấp bởi người gọi (người gọi phân bổ bufvà chuyển đến chức năng)

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

Và gọi nó như vậy:

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
Tùy chọn 3: (một mảng tĩnh)
moooeeeep

5
@moooeeeep: Vâng, tôi đã cố tình để mọi thứ đơn giản, nhưng vâng, bạn có thể trả về một con trỏ tới dữ liệu tĩnh được khai báo từ bên trong hàm.
Ed S.

3
@ user1506919: Tôi thực sự thích tùy chọn 2 vì rõ ràng ai sẽ phân bổ và giải phóng bộ nhớ, nhưng tôi sẽ thêm một ví dụ cho bạn.
Ed S.

7
Tùy chọn 4: Trả về một cấu trúc có chứa một mảng kích thước cố định.
Todd Lehman

2
Tùy chọn 5: Trả về một liên kết có chứa một mảng kích thước cố định.
sqr163

27

Cách xử lý mảng của C rất khác so với Java và bạn sẽ phải điều chỉnh suy nghĩ của mình cho phù hợp. Mảng trong C không phải là đối tượng hạng nhất (nghĩa là một biểu thức mảng không giữ được "mảng" trong hầu hết các ngữ cảnh). Trong C, một biểu thức kiểu "mảng phần tử N T" sẽ được chuyển đổi hoàn toàn ("phân rã") thành biểu thức của kiểu "con trỏ thành T", ngoại trừ khi biểu thức mảng là toán hạng của sizeoftoán tử hoặc toán tử đơn nguyên &hoặc nếu biểu thức mảng là một chuỗi ký tự được sử dụng để khởi tạo một mảng khác trong khai báo.

Trong số những thứ khác, điều này có nghĩa là bạn không thể truyền biểu thức mảng cho hàm và nhận nó dưới dạng kiểu mảng ; Hàm thực sự nhận được một loại con trỏ:

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

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

Trong lệnh gọi đến foo, biểu thức strđược chuyển đổi từ loại char [6]thành char *, đó là lý do tại sao tham số đầu tiên foođược khai báo char *athay vì char a[6]. Trong sizeof str, vì biểu thức mảng là toán hạng của sizeoftoán tử, nên nó không được chuyển đổi thành loại con trỏ, do đó bạn nhận được số byte trong mảng (6).

Nếu bạn thực sự quan tâm, bạn có thể đọc Sự phát triển của ngôn ngữ C của Dennis Ritchie để hiểu cách điều trị này đến từ đâu.

Kết quả cuối cùng là các hàm không thể trả về các kiểu mảng, điều này cũng tốt vì các biểu thức mảng cũng không thể là mục tiêu của một phép gán.

Phương pháp an toàn nhất là để người gọi xác định mảng và chuyển địa chỉ và kích thước của nó cho hàm được cho là ghi vào nó:

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

Một phương thức khác là cho hàm phân bổ mảng một cách linh hoạt và trả về con trỏ và kích thước:

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

Trong trường hợp này, người gọi có trách nhiệm sắp xếp mảng với freechức năng thư viện.

Lưu ý rằng dsttrong đoạn mã trên là một con trỏ đơn giản char, không phải là con trỏ tới một mảng char. Con trỏ và ngữ nghĩa mảng của C sao cho bạn có thể áp dụng toán tử con []cho một biểu thức của kiểu mảng hoặc kiểu con trỏ; cả hai src[i]dst[i]sẽ truy cập iphần tử thứ của mảng (mặc dù chỉ srccó kiểu mảng).

Bạn có thể khai báo một con trỏ tới một mảng phần tử N Tvà làm một cái gì đó tương 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]);
  ...
}

Một số nhược điểm với các bên trên. Trước hết, các phiên bản cũ hơn của C dự kiến SOME_SIZElà hằng số thời gian biên dịch, có nghĩa là hàm đó sẽ chỉ hoạt động với một kích thước mảng. Thứ hai, bạn phải hủy đăng ký con trỏ trước khi áp dụng chỉ mục con, mã này sẽ làm xáo trộn mã. Con trỏ tới mảng hoạt động tốt hơn khi bạn xử lý mảng đa chiều.


2
Liên kết của bạn đến "sự phát triển của C" đã bị hỏng ... có vẻ như nó sẽ hướng chúng tôi đến đây: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso

@Kundor: Những gì barnhận được là một con trỏ, không phải là một mảng. Trong ngữ cảnh của một khai báo tham số hàm, T a[N]T a[]cả hai đều được coi là T *a.
John Bode

@JohnBode: Bạn nói đúng! Vì một số lý do, tôi nghĩ rằng các mảng có kích thước cố định được truyền vào ngăn xếp. Tôi nhớ lại một dịp, nhiều năm trước, khi tôi thấy rằng kích thước của một mảng phải được chỉ định trong chữ ký tham số, nhưng tôi phải bị nhầm lẫn.
Nick Matteo

@JohnBode, trong phần mã thứ hai dòng đầu tiên: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)tham số cuối cùng phải ở size_tkiểu không char.
Seyfi

11

Tôi không nói rằng đây là giải pháp tốt nhất hoặc giải pháp ưa thích cho vấn đề nhất định. Tuy nhiên, có thể hữu ích khi nhớ rằng các hàm có thể trả về các cấu trúc. Mặc dù các hàm không thể trả về các mảng, các mảng có thể được bọc trong các cấu trúc và hàm có thể trả về cấu trúc do đó mang mảng với nó. Điều này làm việc cho các mảng có chiều dài cố định.

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

Tôi mời ý kiến ​​về điểm mạnh và điểm yếu của kỹ thuật này. Tôi không bận tâm để làm như vậy.


1
không rõ tại sao đây không phải là câu trả lời được chấp nhận. Câu hỏi không phải là nếu có thể trả về một con trỏ tới một mảng.
Frank Puck

Là bộ nhớ được phân bổ cho CHAR_ARRAY returnedheap? Nó chắc chắn không thể trên stack (trong khung stack returnArray()phải không?
Minh Tran

9

Làm thế nào về việc thực hiện ác ngon này?

mảng.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;    \
}

C chính

#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
Điều này là cực kỳ ngon đủ để khơi dậy sự tò mò của tôi. Bạn có thể giải thích thêm một chút về những gì bạn đã làm trên đó hoặc có thể đề nghị đọc một món ngon này mà bạn gọi không? Cảm ơn trước.
Unheilig

1
@Unheilig - Lưu ý rằng có những lỗi tiềm ẩn trong việc này, nó chỉ là một bằng chứng về Khái niệm. Điều đó nói rằng, thủ thuật đang trả về một structmảng / đối tượng mảng. Hãy nghĩ về nó giống như một vector Cd std ::. Bộ tiền xử lý sẽ mở rộng intphiên bản này struct intArray { int* contents; int size; };.
pyrospade

1
Tôi thích cách tiếp cận. pro: đây là giải pháp chung chung; contra: giải pháp tăng cường bộ nhớ. Không tối ưu cho các vectơ kích thước kown. Dù sao điều này có thể được nâng cấp với phân bổ kích thước bẩm sinh. Tôi sẽ definitley thêm một số kiểm tra phân bổ. Đề xuất rất tốt để bắt đầu với :)
urkon

Đối tượng pha trộn-esk preossessing mix-mash. Tôi thích nó.
Jack Giffin

6

Trong trường hợp của bạn, bạn đang tạo một mảng trên ngăn xếp và một khi bạn rời khỏi phạm vi hàm, mảng sẽ được giải phóng. Thay vào đó, tạo một mảng được phân bổ động và trả về một con trỏ tới nó.

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
Không có newtoán tử trong C. Đó là C ++.
Eric Postpischil

1
sizeof(char)được đảm bảo 1, vì vậy trong trường hợp này bạn có thể bỏ bit đó từmalloc .
Ed S.

ok vậy Nếu tôi muốn in ra nội dung của mảng mới, tôi có thể thực hiện câu lệnh 'printf' của mình không nhưng thay thế 'returnArray' bằng 'Array'?
user1506919

Bạn không gọi hàm đúng (chỉ một đối số khi chữ ký yêu cầu hai).
Ed S.

Bạn đang đi vào &arr. Bạn muốn arrtrở thành mộtchar * , và sử dụng nó arr.
chris

4

Bạn có thể thực hiện bằng cách sử dụng bộ nhớ heap (thông qua lời gọi malloc () như các câu trả lời khác được báo cáo ở đây, nhưng bạn phải luôn quản lý bộ nhớ (sử dụng hàm free () mỗi khi bạn gọi hàm của mình). Bạn cũng có thể làm điều đó với một mảng tĩnh:

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

// do something in your array here

return array; 
}

Bạn có thể sử dụng nó mà không phải lo lắng về quản lý bộ nhớ.

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

Trong ví dụ này, bạn phải sử dụng từ khóa tĩnh trong định nghĩa mảng để đặt thời gian tồn tại của ứng dụng, vì vậy nó sẽ không bị hủy sau câu lệnh return. Tất nhiên, theo cách này, bạn chiếm SIZE byte trong bộ nhớ của bạn trong toàn bộ vòng đời ứng dụng, vì vậy hãy kích thước nó đúng cách!


2

Phương thức của bạn sẽ trả về một biến ngăn xếp cục bộ sẽ thất bại nặng nề. Để trả về một mảng, hãy tạo một mảng bên ngoài hàm, chuyển nó theo địa chỉ vào hàm, sau đó sửa đổi nó hoặc tạo một mảng trên heap và trả về biến đó. Cả hai sẽ hoạt động, nhưng cái đầu tiên không yêu cầu phân bổ bộ nhớ động để làm cho nó hoạt động chính xác.

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

Bạn có thể sử dụng mã như thế này:

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

Khi bạn làm điều này, bộ nhớ sẽ được giải phóng sau đó, bằng cách chuyển địa chỉ miễn phí.

Có những lựa chọn khác. Một thường trình có thể trả về một con trỏ tới một mảng (hoặc một phần của mảng) là một phần của cấu trúc hiện có. Người gọi có thể truyền một mảng và thường trình chỉ ghi vào mảng, thay vì phân bổ không gian cho một mảng mới.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.