C是一个返回数组的函数

时间:2014-07-22 18:20:02

标签: c

如果我需要编写一个返回数组的函数:int *,哪种方式更好?

  1. int* f(..data..)
  2. 或:void f(..data..,int** arr)

    我们称之为:int* x; f(&x);。 (也许它们都是相同的但我不确定。但是如果我还需要返回一个ErrorCode(它也是一个枚举),那么第一种方式f将获得ErrorCode *并且在第二种方式中,f将返回一个ErrorCode)。

7 个答案:

答案 0 :(得分:5)

返回数组只是返回可变数量的数据 这是一个非常古老的问题,C程序员为此开发了许多答案:

  1. 来电者通过缓冲区。
    1. 记录了必要的大小但没有通过,Undefined Behavior strcpy() strcpy_s() {}}
    2. 记录并传递必要的大小,错误由返回值表示:getline()
    3. 缓冲区大小由指针传递,被调用的函数根据需要重新分配记录的分配器:POSIX snprintf()
    4. 必要的大小未知,但可以通过调用缓冲区长度为0的函数来查询:fread()
    5. 必要的大小是未知的,无法查询,只要返回传递大小的缓冲区即可。如有必要,必须另外打电话来完成剩下的工作:Undefined Behavior
    6. 必要的大小未知,无法查询,传递的缓冲区太小gets()。这是一个设计缺陷,因此在较新版本中不推荐使用该功能,为了完整性,此处仅提及:qsort_s()
  2. 来电者通过回调:
    1. callback-function获取一个context-parameter:qsort()
    2. callback-function没有上下文参数。获取上下文需要魔术:Undefined Behavior
  3. 调用者传递一个分配器:在C标准库中找不到。所有支持分配器的C ++容器都支持它。
  4. Callee合约指定解除分配器。拨错了fopen()fclose() - > strdup() free() - > asctime()
  5. Callee返回包含解除分配器的对象:COM-Objects
  6. Callee使用内部共享缓冲区:madness (UB)
  7. 请注意,返回的数组必须包含sentinel对象或其他标记,您必须单独返回长度,或者必须返回包含指向数据和长度的指针的结构。
    传递引用(指向大小等)可以帮助那里。

    通常,每当用户必须猜测尺寸或在手册中查找时,他有时会弄错。如果他没有弄错,后来的修改可能会使他的细心工作无效,所以他曾经是对的并不重要。无论如何,这种方式是errno

    对于其他人,请选择最舒适,最有效的方式。

    关于错误代码:请记住{{3}}。

答案 1 :(得分:1)

通常返回数组

更方便和语义
int* f(..data..)

如果您需要复杂的错误处理(例如,返回错误值),您应该将错误返回为int,并将数组返回值。

答案 2 :(得分:1)

没有更好的"在这里:您决定哪种方法更适合呼叫者的需求。

请注意,这两个函数都必须为用户提供一个内部分配的数组,因此取消分配结果数组将成为调用者的责任。换句话说,在f()内的某个位置,您将拥有malloc,并且接收数据的用户必须在其上调用free()

你还有另一个选择 - 让调用者将数组传递给你,然后返回一个数字,说明你放回了多少项:

size_t f(int *buffer, size_t max_length)

这种方法允许调用者在静态或自动内存中传递缓冲区,从而提高灵活性。

答案 3 :(得分:1)

经典模型是(假设您还需要返回错误代码)

int f(...., int **arr)

即使它不像返回数组的函数那样流动

请注意,这就是为什么可爱的go语言支持多个返回值。

它也是异常的原因之一 - 它从函数i / o空间中获取错误指标

答案 4 :(得分:1)

如果不需要处理函数中已存在的指针,则第一个更好。 当你已经有一个指向已经分配的容器的定义指针(例如一个列表)并且在函数内部可以改变指针的值时,就会使用第二个。

答案 5 :(得分:0)

如果您必须像f那样致电int* x; f(&x);,那么您没有太多选择。您必须使用第二种语法,即void f(..data..,int** arr)。这是因为您在代码中没有使用返回值。

答案 6 :(得分:0)

该方法取决于具体任务,也可能取决于您的个人品味或项目中采用的编码惯例。

一般情况下,出于多种原因,我想将指针作为“输出”参数而不是return传递给数组。

  1. 您可能希望将数组中的多个元素与数组本身一起返回。但如果你这样做:

    int f(const void* data, int** out_array);
    

    然后,如果你第一次看到签名,你就不能完全知道函数返回的内容,元素的数量或错误代码,所以我更喜欢这样做:

    void f(const void* data, int** out_array, int* out_array_nelements);
    

    甚至更好:

    void f(const void* data, int** out_array, size_t* out_array_nelements);
    

    函数签名必须是不言自明的,参数名称有助于实现这一点。

  2. 输出数组需要存储在某处。您需要为阵列分配一些内存。如果return指向数组的指针而不传递与参数相同的指针,那么不能在堆栈上分配内存。我的意思是,你不能这样做:

    int f (const void *data) {
        int array[10];
        return array;  /* the array is likely deallocated when the function exits */
    }
    

    相反,您必须执行static int array[10](这不是线程安全的)或int *array = malloc(...),这会导致内存泄漏。

    所以我建议你传递一个指向函数调用之前已经分配的数组的指针,如下所示:

    void f(const void *data, int* out_array, size_t* out_nelements, size_t max_nelements);
    

    好处是您可以自由选择分配阵列的位置:

    在堆栈上:

    int array[10] = { 0 };
    size_t max_nelements = sizeof(array)/sizeof(array[0]);
    size_t nelements = 0;
    
    f(data, array, &nelements, max_nelements);
    

    或者在堆中:

    size_t nelements = 0;
    size_t max_nelements = 10;
    int *array = malloc(max_nelements * sizeof(int));
    f(data, array, &nelements, max_nelements);
    

    通过这种方法,您可以自由选择如何分配内存。