我正在开发一个支持多种编程环境的库,如VB6和FoxPro.我必须坚持C约定,因为它是最低的共同点.现在我对这个风格有疑问.
假设函数进程输入并返回一个字符串.在此过程中,可能会发生错误.目前提出的风格是这样的:
int func(input params... char* buffer, unsigned int* buffer_size);
Run Code Online (Sandbox Code Playgroud)
这种风格的好处是原型中包含了所有内容,包括错误代码.并且可以避免内存分配.问题是该功能非常冗长.并且因为buffer_size可以是any,所以需要更多的代码来实现.
另一种选择是返回char*,并返回NULL以指示错误:
char* func(input params...);
Run Code Online (Sandbox Code Playgroud)
此样式需要调用者删除缓冲区.内存分配是必需的,因此服务器程序可能会遇到内存碎片问题.
第二个选项的变体是使用线程局部变量来保存返回的指针char*,这样用户就不需要删除缓冲区.
你喜欢哪种风格?还有原因吗?
谈到这个问题,我有点"受损的商品".我曾经为嵌入式电信设计和维护相当大的API.您不能将任何事情视为理所当然的背景.甚至不是全局变量或TLS.有时甚至堆缓冲区显示实际上是寻址ROM内存.
因此,如果您正在寻找"最低公分母",您可能还需要考虑目标环境中可用的语言结构(编译器可能接受标准C中的任何内容,但如果某些内容不受支持,则链接器会说不.
话虽如此,我总是会选择替代方案1.部分原因(正如其他人所指出的那样),你永远不应该直接为用户分配内存(间接方法将在下面进行解释).即使用户保证使用纯C和纯C,他们仍然可能使用他们自己的自定义内存管理API来跟踪泄漏,诊断日志记录等.通常赞赏支持这样的策略.
在处理API时,错误通信是最重要的事情之一.由于用户可能有不同的方法来处理代码中的错误,因此您应该尽可能保持整个API中的此通信.用户应该能够以一致的方式并使用最少的代码将错误处理包装到您的API中.我通常总是建议使用清晰的枚举代码或定义/ typedef.我个人更喜欢typedef:ed enums:
typedef enum {
RESULT_ONE,
RESULT_TWO
} RESULT;
Run Code Online (Sandbox Code Playgroud)
因为它提供了类型/分配安全性.
具有get-last-error函数也很好(但需要中央存储),我个人仅将其用于提供有关已识别错误的额外信息.
替代方案1 的详细程度可以通过制作这样的简单化合物来限制:
struct Buffer
{
unsigned long size;
char* data;
};
Run Code Online (Sandbox Code Playgroud)
然后你的api可能看起来更好:
ERROR_CODE func( params... , Buffer* outBuffer );
Run Code Online (Sandbox Code Playgroud)
这一战略也为更精细的机制开辟了道路.比如说你必须能够为用户分配内存(例如,如果你需要调整缓冲区的大小),那么你可以提供一个间接的方法:
struct Buffer
{
unsigned long size;
char* data;
void* (*allocator_callback)( unsigned long size );
void (*free_callback)( void* p );
};
Run Code Online (Sandbox Code Playgroud)
当然,这种结构的风格总是可以进行严肃的辩论.
祝好运!
我更喜欢第一个定义,其中缓冲区及其大小被传入.有例外,但通常你不希望在你调用的函数后清理.然而,如果我分配内存并将其传递给函数,那么我知道我必须在自己之后进行清理.
处理不同大小的缓冲区应该不是什么大问题.
第二种变体更干净。
COM IErrorInfo 是第二种方法的实现。服务器调用 SetErrorInfo 设置错误的详细信息并返回错误代码。调用者检查代码并可以调用 GetErrorInfo 来获取详细信息。调用者负责释放 IErrorInfo,但是在第一个变体中传递每个调用的参数也不美观。
服务器可以在启动时预先分配足够的内存,以便它确实有足够的内存来返回错误详细信息。