【问题标题】:Elegant ways to return status code and pointer from a C function从 C 函数返回状态码和指针的优雅方法
【发布时间】:2019-11-27 12:24:29
【问题描述】:

我目前正在阅读this question,它显示了有关使用void ** 作为参数从函数返回指针的问题。

我的代码主要将状态代码作为返回值,现在我正在寻找其他方法来返回这些指针和状态代码。所以我目前看到了几个选择,但没有一个真的让我开心。可能是我想多了。

// Output status through return value and the pointer through parameter 
// - seems to be problematic because it requires casting to void **, which is invalid
int myfunc(void **output);

// Output status through return value, pointer through struct 
// - seems to add unnecessary complexity to the interface
struct some_output { void *value };
int myfunc(struct some_output *output);

// Output pointer through return value, status through parameter 
// - breaks consistency with other interfaces which always return the status code
void *myfunc(int *status);

现在我想知道是否还有其他替代的、优雅的方法可以从我没有想到的没有“缺点”的函数返回指针和状态码?

【问题讨论】:

  • 为什么不返回一个结构体?
  • 您可以返回一个结构,但我认为大多数 C 程序员不会喜欢这个,所以您的第一个解决方案是恕我直言最不意外的解决方案。
  • @Mat 谢谢!那是另一种选择...但是,它仍然以某种方式破坏了与始终返回状态代码的其余界面的一致性:-(
  • 没有任何理由使用void**。命名一个。如果你的函数内部使用动态分配,则返回通过type**分配的类型。
  • “可能我想多了。”不,它反过来。除了“这不仅仅是成功/失败”之外,帖子缺少status 的定义。它可能是一个 3 值 int、一个字符串、一个结构,... 优雅 解决方案需要更多关于您尝试做什么的详细信息。按原样 - 这是未定义的。我们可以花费大量的 cmets(到目前为止 15 个)来梳理这些信息。相反,请考虑删除该帖子,然后再对编码目标进行更清晰的解释。

标签: c pointers void


【解决方案1】:

使用“C”,当函数仅限于返回单个值时,没有一种完美的方法。常用的模式很少,然后是各种可用的 API。考虑坚持使用一种经过验证但不太完美的方法:

  • int status = function(struct Result *output, input);

    • 处理简单的案例,成功/失败的次数很少。
    • 通常使用 0,正数表示成功,负数表示错误。
  • int 结果 = 函数(*输出,输入);带有扩展的错误代码。

    • 用于许多 Linux 系统调用/API,“errno”中有额外的错误详细信息。
    • 通常使用 0,正数表示成功,负数表示错误。
    • 挑战 MT 系统,因为单个错误代码。在许多情况下,错误信息实际上可用作包装线程本地结果的函数。
  • bool 成功 = 函数(*输出,输入);有回调错误

    • 轻松传递成功/失败。
    • 错误信息传递给用户定义的错误回调。
    • 已在许多 GUI 回调(例如 X11)中实现,已经在使用回调。
  • struct result *res = function(input, struct errro **error)

    • 用于 Glib 或其他处理复杂数据类型(不仅仅是数组)的库
    • 通常,每个结构都会有相应的 free* 函数。
    • 错误地址(如果通过)将捕获错误数据。
    • 错误将导致 res = NULL,并设置错误。
    • 在精神上更接近尝试/捕捉。

在引入泛型调用时,我观察到的共同主题通常是将输出和错误对象放在参数列表中的同一位置(不一定要放在一起!)。在许多情况下,输出放在第一位,错误/异常放在最后。

值得关注Error handling in C code

【讨论】:

  • 任何围绕“errno”或“get last error”的废话都将被视为过时的 IMO。我们已经拥有多线程应用程序大约 30 年了,尽管如此,轮询一些全局变量并不是好的 API 设计,从来都不是。规范形式是err_t status = function(struct Result *output, input) ;,其中“err_t”是一些自定义枚举类型。使用 int 或 bool 往往过于生硬。
【解决方案2】:

我同意@Mat 使用:

typedef Gret struct generic_ret;
struct generic_ret {
    int Status;
    union {
     void *p;
     DialPlan *d;
    };
}
...
Phone = GetPhone(...);
if (Phone.Status == 0) {
     Dial(Phone.d);
     ...
}

坚持下去,你最终会用 Go 编程...... 另一种classic status + pointer 的方法是保留比 0 更多的指针值:

extern DialPlan *DP_NoService, *DP_BadNumber, *DP_Broke, ...;
extern bool IsDialError(DialPlan *p);
....
static DialPlan *DP_FirstError = 0,
                *DP_NoService = (void *)1,
                *DP_BadNumber = (void *)2,
                *DP_LastError = (void *)10;
bool IsDialError(DialPlan *p) {
     return (p > DP_FirstError && p < DP_LastError);
}

或者,如果您更喜欢安全的外观:

static DialPlan Errors[10];
static DialPlan *DP_NoService = Errors + 0,
                *DP_BadNumber = Errors + 1,
                ...
                *DP_LastError = Errors + 9;

【讨论】:

  • 除了按值传递结构是不好的做法之外,void* 不需要具有与对象指针相同的表示形式。至于晦涩难懂的指针技巧,我几乎不会称之为“经典”。经典的质量编程是将数据与错误代码分开,并且在出现错误时根本不接触数据。您的整数到指针示例也是非常未定义的行为:您不能对空指针进行指针运算,也不能对指向不同对象的指针进行指针运算。如果你坚持做这些讨厌的事情,至少在进行比较之前转换为整数类型。
  • 经典,如活跃使用数十年。在 1980 年代,C 中的传递和返回结构不再有争议。
  • 是的,C 程序员几十年来一直在调用未定义的行为。你想说什么?至于传递结构,大量嵌入式系统 ABI 在 2019 年产生的代码非常无效。并非每台计算机都是 PC。
  • 不是未定义的;如果您乘坐公共汽车、乘坐火车、乘坐飞机或开车,您将在使用上述两种技术的安全认证内核中运行。
  • 1) (void *)1 可能会失败,因为“结果是实现定义的,可能未正确对齐,可能未指向引用类型的实体,并且可能是陷阱表示。” C11 §6.3.2.3 5. 代码可以使用static DialPlan DP_FirstError;(没有*)。 (我现在在答案的后面部分看到更正) 2)不幸的是(p &gt; DP_FirstError &amp;&amp; p &lt; DP_LastError) 在不正确时是 UB。
猜你喜欢
  • 2010-10-05
  • 2016-01-12
  • 2010-12-25
  • 1970-01-01
  • 2023-03-12
  • 2012-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多