【问题标题】:Regarding typedefs of 1-element arrays in C关于 C 中 1 元素数组的 typedef
【发布时间】:2013-09-01 16:52:52
【问题描述】:

有时,在 C 中,你会这样做:

typedef struct foo {
   unsigned int some_data;
} foo; /* btw, foo_t is discouraged */

要以一种面向对象的方式使用这种新类型,您可能有如下这样的分配/释放对:

foo *foo_alloc(/* various "constructor" params */);
void foo_free(foo *bar);

或者,或者,初始化/清除对(可能返回错误代码):

int foo_init(foo *bar, /* and various "constructor" params */);
int foo_clear(foo *bar);

我见过使用以下成语,特别是在 MPFR 库中:

struct foo {
   unsigned int some_data;
};
typedef struct foo foo[1]; /* <- notice, 1-element array */
typedef struct foo *foo_ptr; /* let's create a ptr-type */

alloc/free 和 init/clear 对现在改为:

foo_ptr foo_alloc(/* various "constructor" params */);
void foo_free(foo_ptr bar);
int foo_init(foo_ptr bar, /* and various "constructor" params */);
int foo_clear(foo_ptr bar);

现在您可以像这样使用它(例如,init/clear 对):

int main()
{  
   foo bar; /* constructed but NOT initialized yet */
   foo_init(bar); /* initialize bar object, alloc stuff on heap, etc. */
   /* use bar */
   foo_clear(bar); /* clear bar object, free stuff on heap, etc. */
}

备注:init/clear 对似乎允许更通用的方式来初始化和清除对象。与 alloc/free 对相比,init/clear 对要求已经构造了一个“浅”对象。 “深度”构造是使用 init 完成的。

问题:单元素数组“type-idiom”有什么不明显的缺陷吗?

【问题讨论】:

    标签: c arrays


    【解决方案1】:

    这很聪明(但见下文)。

    它鼓励了 C 函数参数可以通过引用传递的误导性想法。

    如果我在 C 程序中看到这个:

    foo bar;
    foo_init(bar);
    

    我知道对foo_init 的调用不会修改bar 的值。我也知道代码在未初始化函数时将 bar 的值传递给函数,这很可能是未定义的行为。

    除非我碰巧知道foo 是数组类型的typedef。然后突然意识到foo_init(bar)传递的不是barvalue,而是它的第一个元素的地址。现在,每次我看到一些引用类型 foo 或类型为 foo 的对象时,我都必须考虑如何将 foo 定义为单元素数组的 typedef,然后才能理解代码。

    这是一种尝试让 C 看起来像它不是的东西,就像这样的东西:

    #define BEGIN {
    #define END }
    

    等等。而且它不会导致代码更容易理解,因为它使用了 C 不直接支持的特性。这会导致代码更难理解(尤其是对于熟悉 C 的读者),因为您必须同时理解自定义声明 使整个事情工作的底层 C 语义。

    如果你想传递指针,只需传递指针,并明确地进行。例如,参见FILE*&lt;stdio.h&gt; 中定义的各种标准函数中的使用。没有尝试将指针隐藏在宏或 typedef 后面,C 程序员几十年来一直在使用该接口。

    如果您想编写看起来像通过引用传递参数的代码,请定义一些类似函数的宏,并给它们全大写的名称,以便知识渊博的读者知道发生了一些奇怪的事情。

    我在上面说过这很“聪明”。我想起了我第一次学习 C 语言时所做的一件事:

    #define EVER ;;
    

    这让我写了一个无限循环:

    for (EVER) {
        /* ... */
    }
    

    当时,我觉得这很聪明。

    我仍然认为它很聪明。我只是不再认为这是一件好事。

    【讨论】:

    • 有趣的是,这个问题源于您提到的同样可能的挫败感。也就是说,作为对象类型 Foo 的用户,您可能需要了解底层的 typedef 定义。我试图使用 MPFR 库,因此感到困惑。但我忘记了——我发现它很聪明,只是因为我花了很长时间才理解它:-) 在那个库中,它可能更有意义,因为你需要轻松地创建和初始化小对象(数字) 在堆栈上。但更花哨(聪明)的东西变得棘手。
    • 不错,基思!很高兴阅读您的回答。 SO 需要更多的答案,比如这个,有天赋、风格和额外的东西,比如有趣的个人记忆,给读者的不仅仅是一个答案。
    【解决方案2】:

    这种方法的唯一优点是代码看起来更漂亮,打字更容易。它允许用户在堆栈上创建结构而无需像这样动态分配:

    foo bar;
    

    但是,该结构仍然可以传递给需要指针类型的函数,而无需用户每次都转换为带有&amp;bar 的指针。

    foo_init(bar);
    

    如果没有 1 元素数组,则需要您提到的 alloc 函数或常量 &amp; 用法。

    foo_init(&bar);
    

    我能想到的唯一缺陷是与直接堆栈分配相关的正常问题。如果这在其他代码使用的库中,则对结构的更新可能会在将来破坏客户端代码,而使用 alloc free 对时不会发生这种情况。

    【讨论】:

    • 没错——这也是我的看法。它使符号更简单。
    • 而且不太明显,因为它降低了可读性,并破坏了我们的内部解析器(大脑)。请不要这样做。
    • @H2CO3 我同意,坚持标准的语言语法和用法通常是最好的,即使它涉及更多的输入。
    • 但是typedef struct Foo Foo; 似乎被接受了?我们可以看到它,相当简化,因为从类型名称中删除了单词“struct”。可能不鼓励使用 typedef 隐藏特定语言功能(例如使用数组)的其他用途。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-09
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-15
    • 2013-07-20
    相关资源
    最近更新 更多