【问题标题】:pointer as second argument instead of returning pointer?指针作为第二个参数而不是返回指针?
【发布时间】:2010-03-26 02:13:44
【问题描述】:

我注意到在 C 中接受 un-malloced 指针作为第二个参数而不是返回指针是一种常见的习惯用法。示例:

/*function prototype*/    
void create_node(node_t* new_node, void* _val, int _type);

/* implementation */
node_t* n;
create_node(n, &someint, INT)

代替

/* function prototype */
node_t* create_node(void* _val, int _type)

/* implementation */
node_t* n = create_node(&someint, INT)

这两种方法的优点和/或缺点是什么?

谢谢!

编辑谢谢大家的回答。选择 1 的动机现在对我来说非常清楚(我应该指出,选择 1 的指针参数应该 malloc'd 与我最初的想法相反)。

【问题讨论】:

    标签: c pointers


    【解决方案1】:

    这没有多大意义。 C 中的指针是按值传递的,就像其他对象一样——区别在于值。对于指针,值是传递给函数的内存地址。但是,您仍在复制值,因此当您 malloc 时,您将更改函数内部指针的值,而不是外部指针的值。

    void create_node(node_t* new_node, void* _val, int _type) {
        new_node = malloc(sizeof(node_t) * SIZE);
        // `new_node` points to the new location, but `n` doesn't.
        ...
    }
    
    int main() {
        ...
        node_t* n = NULL;
        create_node(n, &someint, INT);
        // `n` is still NULL
        ...
    }
    

    有三种方法可以避免这种情况。正如您提到的,第一个是从函数返回新指针。第二种是获取指向指针的指针,从而通过引用传递:

    void create_node(node_t** new_node, void* _val, int _type) {
        *new_node = malloc(sizeof(node_t) * SIZE);
        // `*new_node` points to the new location, as does `n`.
        ...
    }
    
    int main() {
        ...
        node_t* n = NULL;
        create_node(&n, &someint, INT);
        // `n` points to the new location
        ...
    }
    

    第三种就是简单的在函数调用之外mallocn

    int main() {
        ...
        node_t* n = malloc(sizeof(node_t) * SIZE);
        create_node(n, &someint, INT);
        ...
    }
    

    【讨论】:

    • 感谢您纠正我理解中的重大错误。 :)
    • 我应该补充一点,我同意 Alex Martelli 的观点:第三种方法对于大多数情况来说绝对是最好的。
    【解决方案2】:

    接受一个指向要填充的内存的指针(调用者是否负责 malloc'ing)在灵活性方面提供了比 返回指针(必须 malloc'ed)。特别是,如果调用者知道它需要使用仅在某个函数中返回的任何内容,它可以传入堆栈分配的结构或数组的地址;如果它知道它不需要重入,它可以传入 static 结构或数组的地址——在任何一种情况下,都会保存一个 malloc/free 对,并且这样的节省确实会增加!-)

    【讨论】:

    • 这个。做struct thing t; init_thing(&t); 的能力很好。
    • 如果我想写一个函数来复制一个链表,我会有像void copy_list(node_t* new_head, node_t* original_head)这样的函数原型吗?我想我必须为原始列表中的每个节点分配一个新节点并将其添加到 new_head 的“末尾”(复制 val 和类型,但给它一个新的 node_t* next 指针值)。这是最好的方法吗?
    • @Tyler,将copy_list(node_t **new_head, const node_t* original_head) 视为该函数可能更可取的签名(尽管这种特殊情况返回 node_t * 肯定也是一种合理的可能性)。
    • @Alex 你会使用哪一个/你经常看到哪一个?
    • @Tyler,在其他人的代码中,我看到很多“返回指针”(用于复制列表等任务)——当我为现有库或程序做出贡献时,我遵循与其他现有代码,当我从头开始编写代码时,我通常更喜欢传递指针到指针,准确地使用const,并将返回值用作“成功或错误”代码(例如许多 Unix 系统调用)。跨度>
    【解决方案3】:

    我通常更喜欢接收指针(属性初始化)作为函数参数,而不是返回指向已在函数内部 malloc'ed 的内存区域的指针。通过这种方法,您可以明确表示内存管理的责任在用户一方。

    返回指针通常会导致内存泄漏,因为如果您没有对其进行 malloc() 处理,则更容易忘记释放指针。

    【讨论】:

    • "如果您没有 malloc() 处理指针,则更容易忘记释放指针。"不确定我是否同意这一点,但我在一个没有在进程退出时释放内存的系统上工作了 7 年。这教你如何编写无泄漏代码。
    • 我的意思不是像...“总是”,但也许新手不会想到函数返回的指针已经被分配在其中。这更像是一种品味:]
    • @Steve 系统是什么,它的优势是什么?
    • @Tyler:嵌入式/托管/奇怪的便携式 RTOS,称为“意图”。各种优点/必需品。内存开销:系统只需要一个全局内存分配器,没有每个进程的堆。它没有虚拟内存或内核空间/模式,所以一切都在进程上下文中。信号只是勉强支持,并且大部分时间都被禁用,所以你可以假设/安排你不会因此而被杀死和泄漏。 mallocfree 确实在每个进程的基础上跟踪内存并在退出时释放,并且是信号安全的,但大多数系统编程使用较低级别的分配。
    • @mgv:是的,我想我知道你的意思,只是我的训练基本上是“永远,永远在不知道(a)资源所有权的情况下调用函数,(b)它的所有错误模式, (c) 是否可以在禁用信号的情况下调用它, (d) 是否可以在信号启用的情况下调用它, (e) 是否可以在中断上下文中调用它, 如果不能, (f)还有其他方法可以解决这个问题吗?(g)等等”。
    【解决方案4】:

    我通常不会做出固定的选择,我将它干净利落地放入自己的库中,并提供两全其美的选择。

    void node_init (node_t *n);
    
    void node_term (node_t *n);
    
    node_t *node_create ()
    {
        node_t *n = malloc(sizeof *n);
        /* boilerplate error handling for malloc returning NULL goes here */
        node_init(n);
        return n;
    }
    
    void node_destroy (node_t *n)
    {
        node_term(n);
        free(n);
    }
    

    每个 malloc 都应该有一个 free,因此每个 init 应该有一个 term,每个 create 应该有一个 destroy。随着您的对象变得越来越复杂,您会发现您开始嵌套它们。一些更高级别的对象可能使用 node_t 列表进行内部数据管理。在释放此对象之前,必须先释放列表。 _init 和 _term 关心这个,完全隐藏了这个实现细节。

    可以就更多细节做出决定,例如destroy 可能需要一个 node_t **n 并在释放它后将 *n 设置为 NULL。

    【讨论】:

      【解决方案5】:

      我个人喜欢使用引用或指针参数返回数据,并使用函数return返回错误代码。

      【讨论】:

        【解决方案6】:

        1)正如Samir指出的代码不正确,指针是按值传递的,你需要**

        2) 该函数本质上是一个构造函数,因此分配内存和初始化数据结构对它来说都是有意义的。干净的 C 代码几乎总是像构造函数和析构函数一样面向对象。

        3) 你的函数是无效的,但它应该返回 int 以便它可以返回错误。至少有 2 种,可能 3 种可能的错误情况:malloc 可能失败,类型参数可能无效,值可能超出范围。

        【讨论】:

          【解决方案7】:

          本文未讨论的一个问题是,您如何在分配它的函数中引用 malloc 的缓冲区,并且可能在将控制权返回给调用者之前在其中存储一些内容。

          在将我带到此页面的情况下,我有一个传入指针的函数,该指针接收 HOTKEY_STATE 结构数组的地址。原型声明参数如下。

          HOTKEY_STATE ** plplpHotKeyStates
          

          返回值,ruintNKeys,是数组中的元素个数,在分配缓冲区之前由例程确定。但是,我没有直接使用 malloc(),而是使用了 calloc,如下所示。

          *plplpHotKeyStates = ( HOTKEY_STATE * ) calloc ( ruintNKeys ,
                                                           sizeof ( HOTKEY_STATE ) ) ;
          

          在我验证plplpHotKeyStates不再为null后,我定义了一个局部指针变量hkHotKeyStates,如下。

          HOTKEY_STATE * hkHotKeyStates = *plplpHotKeyStates ;
          

          使用此变量,下标为无符号整数,代码使用简单的成员运算符 (.) 填充结构,如下所示。

          hkHotKeyStates [ uintCurrKey ].ScanCode = SCANCODE_KEY_ALT ;
          

          当数组被完全填充时,它会返回废墟NKeys,并且调用者拥有处理数组所需的一切,或者以传统方式,使用引用运算符 (->),或者使用我使用的相同技术在函数中直接访问数组。

          【讨论】:

            猜你喜欢
            • 2011-08-10
            • 2018-04-23
            • 2018-05-30
            • 1970-01-01
            • 2019-08-22
            • 1970-01-01
            • 2019-11-18
            • 1970-01-01
            • 2016-03-19
            相关资源
            最近更新 更多