有哪些缺陷使您无法使用C API(包括标准库,第三方库和项目内的标头)?目的是确定C语言中的API设计陷阱,以便编写新C库的人们可以从过去的错误中学习。
解释为什么缺陷不好(最好举个例子),并尝试提出改进建议。尽管您的解决方案在现实生活中可能不切实际(修复为时已晚strncpy
),但它应该提请未来的图书馆作家。
尽管此问题的重点是C API,但是仍然存在影响您以其他语言使用它们的能力的问题。
请为每个答案给出一个缺陷,以便民主可以对答案进行排序。
有哪些缺陷使您无法使用C API(包括标准库,第三方库和项目内的标头)?目的是确定C语言中的API设计陷阱,以便编写新C库的人们可以从过去的错误中学习。
解释为什么缺陷不好(最好举个例子),并尝试提出改进建议。尽管您的解决方案在现实生活中可能不切实际(修复为时已晚strncpy
),但它应该提请未来的图书馆作家。
尽管此问题的重点是C API,但是仍然存在影响您以其他语言使用它们的能力的问题。
请为每个答案给出一个缺陷,以便民主可以对答案进行排序。
Answers:
返回值不一致或不合逻辑的函数。两个很好的例子:
1)一些返回HANDLE的Windows函数将NULL / 0用于错误(CreateThread),一些将INVALID_HANDLE_VALUE / -1用于错误(CreateFile)。
2)POSIX的“时间”函数在出错时返回“(time_t)-1”,这是不合理的,因为“ time_t”可以是有符号或无符号类型。
int time(time_t *out);
和BOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
。
具有非描述性或肯定性混淆名称的函数或参数。例如:
1)Windows API中的CreateFile实际上并没有创建文件,而是创建了文件句柄。如果通过参数要求,它可以像“打开”一样创建文件。此参数具有名为“ CREATE_ALWAYS”和“ CREATE_NEW”的值,它们的名称甚至不暗示其语义。(“ CREATE_ALWAYS”是否意味着如果文件存在会失败?或者它会在文件顶部创建一个新文件?“ CREATE_NEW”意味着它会始终创建一个新文件并且如果该文件已经存在会失败吗?还是会创建一个新文件?文件放在上面?)
2)POSIX pthreads API中的pthread_cond_wait,尽管其名称如此,但它是无条件的等待。
通过接口作为类型删除句柄传递的不透明类型。当然,问题在于编译器无法检查用户代码中是否有正确的参数类型。
这有多种形式和风味,包括但不限于:
void*
滥用
使用int
作为资源句柄(比如:CDI库)
字符串型参数
不同的类型(=不能完全互换使用)映射到同一类型的已删除类型越糟。当然,补救措施只是沿(C示例)的行提供类型安全的不透明指针:
typedef struct Foo Foo;
typedef struct Bar Bar;
Foo* createFoo();
Bar* createBar();
int doSomething(Foo* foo);
void somethingElse(Foo* foo, Bar* bar);
void destroyFoo(Foo* foo);
void destroyBar(Bar* bar);
具有不一致且通常麻烦的字符串返回约定的函数。
例如,getcwd询问用户提供的缓冲区及其大小。这意味着应用程序必须在当前目录长度上设置一个任意限制,或者执行以下操作(来自CCAN):
/* *This* is why people hate C. */
len = 32;
cwd = talloc_array(ctx, char, len);
while (!getcwd(cwd, len)) {
if (errno != ERANGE) {
talloc_free(cwd);
return NULL;
}
cwd = talloc_realloc(ctx, cwd, char, len *= 2);
}
我的解决方案:返回一个malloc
ed字符串。它简单,强大且效率不低。除了嵌入式平台和较旧的系统,malloc
实际上是相当快的。
snprintf(buf, 32, "%d", n)
输出长度是可预测的(例如,除非您的系统上确实很大),输出长度是可预测的(例如,不超过30),int
这很好。确实,malloc在许多系统上都不可用,但是对于台式机和服务器环境而言,它确实是有效的。
按值获取/返回复合数据类型或使用回调的函数。
如果所说的类型是并集或包含位字段,则更糟。
从C调用者的角度来看,这些实际上是可以的,但是除非有必要,否则我不会用C或C ++编写,因此我通常通过FFI进行调用。大多数FFI不支持联合或位域,而某些FFI(例如Haskell和MLton)不支持按值传递的结构。对于那些可以处理按值结构的对象,至少将Common Lisp和LuaJIT强制设置为慢速路径-Lisp的Common Foreign Function Interface必须通过libffi进行慢速调用,而LuaJIT拒绝JIT编译包含该调用的代码路径。可能会回调到主机的函数也会触发LuaJIT,Java和Haskell上的慢速路径,而LuaJIT无法编译此类调用。
malloc
'd字符串如何解决该问题。我认为为第一个答案树立榜样确实可以使这个问题蓬勃发展。谢谢!