API 在 C 中模拟返回数组的函数

API in C to mimic a function returning an array

我正在尝试找出一种方法来解决使用 C 语言返回数组(固定大小的非常短的字符串)的函数的问题。

在我的例子中,我需要向我的用户公开一个函数,该函数会如同它正在返回:

typedef char (bytes2_t)[2];
bytes2_t get_val(struct opaque *opaque);

我能够想出的唯一解决方案(我选择了 union 来实现,我也可以使用 struct)如下(伪代码):

typedef union {
  char bytes[2];
  uint16_t val;
} uval_t;
/* intermediate function */
static inline uval_t get_val_impl(struct opaque *opaque) {
  uval_t uval;
  uval.val = 16961; /* opaque->val */
  return uval;
}
/* Actual API */
#define get_val(opaque) get_val_impl(opaque).bytes

在我的用例中效果很好:

struct opaque opaque;
printf("%.2s\n", get_val(&opaque));

您将如何定义一个 API,如果它返回一个固定大小的短数组(2 或 4 个字符)而没有宏污染全局命名空间?API?

请不要尝试re-invent C 语言通过宏。这会导致不直观和令人困惑的 APIs。相反,让调用者将缓冲区作为参数传递,就像其他所有 C 程序所做的那样。

如果由于某种未知原因无法完成,second-most 明智的解决方案是 return 一个结构,通过值或通过指针。

另一个常见但不太优雅的设计是有一个指针参数,其中分配的存储被传递给函数,然后 return 一个指向该参数的指针。 (就像 strcpy 等函数一样)

最糟糕的选择是 return 一个 malloc 硬拷贝指针,如本答案底部所述。


在这种特定情况下,您实际上并未创建具有私有封装的不透明类型,这就是该宏起作用的原因。相反,你应该有这样的东西:

// opaque.h

typedef struct opaque opaque;

opaque* opaque_create (void);

void opaque_free (opaque* obj)

void opaque_get_bytes (const opaque* obj, char bytes[2]);

// opaque.c

struct opaque
{
   // private members
   char bytes[2];
};

opaque* opaque_create (void)
{
  opaque* obj = malloc (sizeof *obj);
  obj->bytes[0] = 'A';
  obj->bytes[1] = 'B';
  return obj;
}

void opaque_free (opaque* obj)
{
  free(obj);
}

void opaque_get_bytes (const opaque* obj, char bytes[2])
{
  bytes[0] = obj.bytes[0];
  bytes[1] = obj.bytes[1];
}

// caller.c

#include "opaque.h"

opaque* op = opaque_create();

char buf[2];
opaque_get_bytes(op, buf);

如果您坚持通过 return 值 returning 数组 - 它必须是硬拷贝,因为我们不应该通过指针公开私有成员 - 那么您最终要负责这些字节的分配:

// bad idea
char* opaque_get_bytes (const opaque* obj)
{
  char* result = malloc (sizeof char[2]);
  result[0] = obj.bytes[0];
  result[1] = obj.bytes[1];
  return result;
}

现在我们创建了一个 icky API,其中调用者负责释放这些字节。正如之前在其他令人厌恶的、内存泄漏的 C 程序中看到的那样。最好始终将分配留给调用者。这就是为什么我们应该避免 returning 函数指针的真正原因 - 有有效的特殊情况,比如不透明类型本身 - 但大多数时候它只是不好的做法。

典型的 C89 编译器,给定如下结构:

doSomething(functionReturningStructure().someArray);

通常会获取用于保存 return 值的临时对象的地址,将其替换为数组的偏移量,并将该结果视为表达式 functionReturningStructure().someArray 的值它可以决定用那个临时结构完成它。有时这会导致被调用函数接收可用数组的地址,有时则不会。

C99 试图确定延长生命周期的规则,但尚不清楚由此产生的生命周期应该是多少。可以将标准解读为要求编译器,给定:

(f1() ? f2(f3().array) : f4(f5.array())) || f5();

需要确保 f3().arrayf5().array 的生命周期——以实际评估的为准——必须通过对 f5() 的调用来延长,一些编译器可能会这样做那个,但是正确和有效地处理所有涉及可能创建或可能不创建的对象的生命周期的极端情况将需要一定程度的编译器复杂性,远远超出标准作者可能预期的任何水平。

有可能所有声称与 C99 兼容的 currently-maintained 编译器都能正确处理所有必要的极端情况,但我认为这些东西甚至是声称与 C99 兼容的编译器的构造不应依赖于以任何特定方式进行处理,即使对标准的合理直接阅读表明他们必须这样做。