使用C返回一个数组

215

我对C语言比较陌生,需要一些有关数组函数的帮助。从Java编程来看,我习惯于使用int [] method()来返回一个数组。但是,我发现在C中,当你要返回数组时,你必须使用指针。作为新手程序员,我真的完全不理解这个,即使我查阅了许多论坛。

基本上,我正在尝试编写一个在C中返回char数组的方法(我们称之为returnArray),并将提供一个数组给该方法。它将从先前的数组创建一个新数组,并返回指向该数组的指针。我只需要一些关于如何启动这个过程以及如何读取指针的帮助。

数组返回函数的建议代码格式

char *returnArray(char array []){
  char returned [10];
  // Methods to pull values from the array, interpret
  // them, and then create a 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编译器无法工作,所以我还没有测试过这个,但我想找出其中的问题。


你的代码示例中返回的数组大小是否已知?除了答案中提到的堆栈问题之外,我看到的唯一问题是,如果你的返回数组大小不确定,由于指针/数组在C中的工作方式,你将不知道它有多大。 - strangefreeworld
是的,我始终知道传入数组的大小。输入和输出数组的大小不会改变。 - user1506919
1
C语言的发展* - https://www.bell-labs.com/usr/dmr/www/chist.html - x4444
对于初学者返回函数局部堆栈的(无效)引用错误,经典问题可能是 *在C++中返回对局部变量的引用*。虽然这个问题可能隐藏在前三年的问题中,但搜索引擎非常不愿意(出于任何原因)指向它们(这并不意味着它们过时)。 - Peter Mortensen
8个回答

299

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

47
选项3:(一个静态数组) - moooeeeep
5
是的,我有意将其省略以保持简单,但是你确实可以从函数内部返回指向静态数据的指针。 - Ed S.
4
@user1506919:我实际上更喜欢选项2,因为它清楚地表明了谁分配和释放内存,但我会为您添加一个示例。 - Ed S.
15
选项4:返回包含固定大小数组的结构体。 - Todd Lehman
2
选项5:返回包含固定大小数组的联合。 - sqr163
显示剩余23条评论

45
C语言中对数组的处理与Java截然不同,因此你需要相应地调整思维方式。在C语言中,数组不是一级对象(也就是说,在大多数情况下,数组表达式不会保留其 "数组性")。在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函数时,表达式strchar [6]类型转换为char *类型,这就是为什么foo函数的第一个参数声明为char *a而不是char a[6]的原因。在sizeof str中,由于数组表达式是sizeof运算符的操作数,所以它不会被转换为指针类型,因此你得到的是数组的字节数(6)。
如果您真的感兴趣,可以阅读Dennis Ritchie的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具有数组类型)。
您可以声明一个指向T的N元素数组的指针,并进行类似操作:
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必须是编译时常量,这意味着该函数只能适用于一个数组大小。其次,在应用下标之前必须解引用指针,这会使代码变得混乱。当处理多维数组时,指向数组的指针效果更好。


3
你提供的“C语言发展史”链接已经失效了...看起来应该指向这里:https://www.bell-labs.com/usr/dmr/www/chist.html - Dr.Queso
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - John Bode
@JohnBode:你说得对!出于某种原因,我认为固定大小的数组是通过堆栈传递的。我记得很多年前有一次,我发现数组的大小必须在参数签名中指定,但我可能当时感到困惑了。 - Nick Matteo
@JohnBode,在第二段代码的第一行中:void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize),最后一个参数应该是 size_t 类型而不是 char - Seyfi

25

我并不是说这是解决问题的最佳方法或首选方法。然而,值得记住的是函数可以返回结构体。虽然函数不能返回数组,但可以将数组封装在结构体中,函数可以返回该结构体从而携带数组。这对于固定长度的数组有效。

    #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
不清楚为什么这不是被接受的答案。问题不是是否可以返回数组指针。 - user12411795
1
CHAR_ARRAY分配的内存是在堆上返回的吗?它肯定不可能在栈上(在returnArray()的堆栈帧中)返回。 - Minh Tran
是的,这就是我问题的答案:C函数可以返回一个数组吗?是的,它可以,并且@Indinfer使用了C自己的结构数据类型来回答。当然,它应该是固定长度的数组。这是C语言,你必须事先确定性,除非你有时间去玩指针、地址、malloc、free等等,只是为了一个简单的函数返回。干杯。 - KokoEfraim
@MinhTran 参考 https://godbolt.org/z/1rYocv3PT - 实际上,ring_slice 被转换为一个接受地址存储的函数。您可以看到 main 在堆栈上为 Mem2 保留了 32 字节 (sub rsp, 32) 并通过 rdi 将其地址传递给 ring_slice。我不太清楚我的调用约定,但我认为 rdi 通常是函数的第一个参数。然后,ring_slice 将其结果存储在那里并返回相同的地址 (mov rax, rdi)。 - ekipan

12

您可以使用堆内存(通过 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 */
}

在这个例子中,您必须在数组定义中使用static关键字来设置数组的生存期为整个应用程序期间,因此它不会在返回语句后被销毁。
当然,以这种方式,您将在整个应用程序生命周期中占用SIZE字节的内存,因此请适当调整大小!

把指针交给函数内存是多么好啊?别管多线程,这在串行代码中很糟糕。 - user426
这个页面上有很多关于如何解决这个问题的建议,但我发现使用“static”来返回数组是最好的方法,只要你意识到从那时起返回的值是一个全局变量。 - sueszli

12

使用这个美妙邪恶的实现:

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

主函数.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;
}

4
这个东西真是魔鬼般的美味,足以激起我的好奇心。你能再解释一下你做了什么,或者推荐一些关于这种美味的阅读材料吗?先谢谢了。 - Unheilig
1
@Unheilig - 请注意,这里可能存在一些潜在的错误,这只是一个概念验证。话虽如此,技巧在于将struct作为数组容器/对象返回。可以将其视为C++中的std::vector。预处理器会将此的int版本扩展为struct intArray { int* contents; int size; }; - pyrospade
1
我喜欢这种方法。优点:这是通用解决方案;缺点:内存占用较高,对于已知大小的向量不是最优解。无论如何,这可以通过初始大小分配进行升级。我肯定会添加一些分配检查。非常好的建议开始 :) - urkon
1
面向对象的预处理混搭。我喜欢它。 - Jack G
这让我想起了stb_ds - bhathiya-perera

8

在您的情况下,您正在堆栈上创建一个数组,一旦离开函数作用域,该数组将被释放。相反,创建一个动态分配的数组并返回指向它的指针。

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

}

3
在C语言中没有new运算符,这是C++特有的。 - Eric Postpischil
1
而且 sizeof(char) 被保证为 1,所以在这种情况下,您可以从 malloc 中省略该位。 - Ed S.
好的,如果我想打印出新数组的内容,我是否可以使用我的“printf”语句,但将“returnedArray”替换为“arr”? - user1506919
你正在传递 &arr。你希望arr成为一个 char*,并使用 arr 进行传递。 - chris
@ManofOneWay,哦,我的错。那么arr应该是一个char[],并且仍然通过arr传递。 - chris
显示剩余4条评论

3

你的方法将返回一个本地栈变量,这可能会导致严重错误。为了返回一个数组,请在函数外创建一个数组,通过地址传递到函数中并进行修改,或者在堆上创建一个数组并返回该变量。两种方法都可以使用,但第一种方法不需要任何动态内存分配就可以正确工作。

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

1
您可以使用类似以下的代码:
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.
}

当您执行此操作时,应该通过将地址传递给 free 来释放内存。
还有其他选项。例程可能会返回指向数组(或某个数组的一部分)的指针,该数组是某个现有结构的一部分。调用者可能会传递一个数组,而例程仅仅写入这个数组,而不是为新数组分配空间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接