C一个返回数组的函数

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

  1. int* f(..data..)

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

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

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

  1. 来电者通过缓冲区。
    1. 必要的大小是记录的,不会传递,太短的缓冲区是未定义的行为 : strcpy()
    2. 记录并传递必要的大小,错误由返回值发出信号: strcpy_s()
    3. 缓冲区大小由指针传递,被调用的函数根据需要与记录的分配器重新分配:POSIX getline()
    4. 必要的大小是未知的,但可以通过调用缓冲区长度为0的函数来查询: snprintf()
    5. 必要的大小是未知的,无法查询,只要返回传递大小的缓冲区。 如果需要,必须进行额外的调用以获得其余的: fread()
    6. 必要的大小是未知的,无法查询,并且传递太小的缓冲区是未定义的行为 。 这是一个设计缺陷,因此在较新版本中不推荐使用该函数,为了完整性,这里仅提及: gets()
  2. 呼叫者通过回调:
    1. callback-function获取一个context-parameter: qsort_s()
    2. callback-function没有上下文参数。 获取上下文需要魔术: qsort()
  3. 调用者传递一个分配器:在C标准库中找不到。 但是,所有支持allocator的C ++容器都支持它。
  4. Callee合约指定解除分配器。 调用错误的是未定义的行为 : fopen() – > fclose() strdup() – > free()
  5. Callee返回一个包含解除分配器的对象:COM-Objects
  6. Callee使用内部共享缓冲区: asctime()

请注意,返回的数组必须包含sentinel对象或其他标记,您必须单独返回长度,或者必须返回包含指向数据和长度的指针的结构。
传递引用(指向大小等)有助于那里。

一般来说,每当用户不得不猜测尺寸或在手册中查找时,他有时会弄错。 如果他没有弄错,后来的修改可能会使他的小心工作无效,所以他曾经是对的并不重要。 无论如何,这种方式就是疯狂(UB) 。

其余的,选择最舒适,最有效的。

关于错误代码:记住有errno

通常,返回数组更方便和语义

 int* f(..data..) 

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

这里没有“更好”:您可以更好地确定哪种方法更符合呼叫者的需求。

请注意,这两个函数都绑定为用户提供他们在内部分配的数组,因此取消分配结果数组成为调用者的责任。 换句话说,在f()某个地方你会有一个malloc ,接收数据的用户必须在其上调用free()

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

 size_t f(int *buffer, size_t max_length) 

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

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

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

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

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

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

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

如果你必须调用fint* x; f(&x); int* x; f(&x); ,你没有太多的选择。 您必须使用第二种语法,即void f(..data..,int** arr) 。 这是因为您的代码中没有使用返回值。

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

一般来说,我想将指针作为“输出”参数传递,而不是由于多种原因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. 输出数组需要存储在某处。 您需要为arrays分配一些内存。 如果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); 

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

    在堆栈上:

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

    请参阅此方法,您可以自由选择如何分配内存。