问题描述
我正在尝试通过函数以C返回数组(固定大小非常短的字符串)的方法来解决该问题。
在我的情况下,我需要向用户公开一个函数,该函数将在返回时执行如果:
typedef char (bytes2_t)[2];
bytes2_t get_val(struct opaque *opaque);
我能想到的唯一解决方案(我选择了实现的联合,也可以使用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会起到什么作用?
解决方法
请不要尝试通过宏重新发明C语言。这会导致不直观且令人困惑的API。而是让调用者像其他所有C程序一样,将缓冲区作为参数传递。
如果由于某种原因无法执行未知操作,那么第二个明智的解决方案是通过值或通过指针返回结构。
另一种常见但不太优雅的设计是拥有一个指针参数,该指针将分配的存储传递给该函数,然后还返回一个指向该参数的指针。 (就像strcpy
等功能一样)
最糟糕的选择是返回一个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);
如果您坚持要通过返回值返回一个数组-它必须是硬拷贝,因为我们不应该通过指针公开私有成员-那么您最终将负责分配这些字节:
// 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程序中看到的数千倍之前一样。最好始终将分配留给调用方。这就是为什么我们应该避免从函数返回指针的真正原因-存在有效的特殊情况,例如opaque类型本身-但在大多数情况下,这完全是错误的做法。
,典型的C89编译器,其结构如下:
doSomething(functionReturningStructure().someArray);
通常将使用用于保存返回值的临时对象的地址,将其移位数组的偏移量,并将该结果视为表达式functionReturningStructure().someArray
的值,以便它可以确定已完成那个临时结构。有时这会导致被调用的函数接收到可用数组的地址,有时则不会。
C99试图确定规则以延长寿命,但是还不清楚最终的寿命应该是多少。鉴于以下情况,人们可能会认为标准要求编译器:
(f1() ? f2(f3().array) : f4(f5.array())) || f5();
需要确保f3().array
或f5().array
的生命周期(无论是实际评估的生命周期)必须通过对f5()
的调用来延长,某些编译器可能会这样做,但是正确,有效地处理涉及可能创建或可能无法创建的对象的生命周期的所有极端情况,将需要使编译器的复杂性水平远远超出标准作者的预期范围。
所有当前声称兼容C99的编译器都有可能正确地正确处理所有必要的极端情况,但我认为这样的结构即使是声称兼容C99的编译器也不应依赖继续以任何特定方式进行处理,即使对该标准进行了相当直接的阅读也表明他们必须这样做。