繁体   English   中英

从函数返回对象的最佳方法是什么(C)

[英]What is the best way to return an object from a function (C)

这在某种程度上是一个主观问题,但似乎应该有一个标准。 我正在制作树状数据结构,我想知道从函数传递新节点的最佳方法。 我有几个想法,但我不知道哪个是最安全/最有效的。

这是我的代码的简化:

typedef struct Node {
    struct Node *left;
    struct Node *right;
    int value;
} Node;

int f() {
    //do stuff
}

Node *new_node() {
    Node n = {NULL, NULL, f()};
    return &n;
}

int main() {
    Node a = {new_node(), new_node(), 0};
}

显然,这不起作用,因为new_node()函数返回的指针指向堆栈分配的数据,这些数据将在new_node()结束后立即释放。 但是解决这个问题的最佳方法是什么?

一种可能性是在堆上分配n ,如下所示:

Node *new_node() {
    Node *n = (Node *) malloc(sizeof(Node)); //unsure if the cast is necessary here, but that's not relevant
    n->left = NULL;
    n->right = NULL;
    n->value = f();
    return n;
}

但这感觉不对,因为它要求调用者函数直接处理内存清除,这可能会很快变得混乱。

我见过的另一个选项(特别是当对象是数组或缓冲区而不是对象时)是传递指向函数的指针,只是修改指针的内容。

void new_node(Node *n) {
    n->left = NULL;
    n->right = NULL;
    n->value = f();
}

int main() {
    Node n = {NULL, NULL, 0};
    Node n1 = n;
    new_node(&n);
    new_node(&n1);
    Node a = {&n, &n1, 0};
}

或者你可以直接传递数据,特别是因为在这种情况下Node非常小:

 Node new_node() {
     Node n = {NULL, NULL, f()}
     return n;
 }

这似乎会慢一点,虽然我不确定。

关于这个主题已经有了一些答案,但它们都是用C ++编写的,并且处理引用和指针,我恐怕不太了解它们之间的区别。

这样做的标准方法是什么?

(对不起,如果这太长了)

您涵盖了所有四种可用方法,包括一种无效方法。 在他们之间挑选取决于您的设计偏好:

  • 您正确地指出第一种方法已被破坏
  • 第二种方法适用于您创建的对象无法静态分配的情况,例如,因为直到运行时才知道您需要多少个对象
  • 第三种方法适用于初始化静态分配的对象,因为它不需要复制,它可以让您避免手动资源管理
  • 由于希望避免复制,最后一种方法不太常见。 这几乎普遍等于过早优化,因此这种方法也是完全有效的。

既然你问过, 我是否投出了malloc的结果? 没有。

至于你关注的问题,所有方法都有它们的优点和缺点,所以选择一个用于你的项目并保持对该决定的承诺(不要对一个函数使用一种方法,然后对树的另一个函数使用另一种方法)。

所有权可以是任何一个函数(但调用者必须调用将取消分配树的那个),或者调用者自己。

例如,在库中,我希望它们处理数据结构的内存分配,然后我只需调用库本身提供的函数来解除分配空间。 这样我就不会进入他们的内部细节,这很好。

通常赋予函数所有权,但这是主观的。

这里的基本决定是谁控制记忆。 你是否让呼叫者决定并将更多的工作转移给他们? 或者你为他们处理?

为什么不兼得? 从OO构造函数模式中获取页面,将创建与初始化分开。

void *Node_init( Node *node ) {
    node->left = NULL;
    node->right = NULL;
    node->value = 0;
}

Node *Node_new() {
    Node *node = malloc(sizeof(Node));

    Node_init(node);

    return node;
}

void *Node_free( Node *node ) {
    free(node);
}

请注意,我没有硬编码任何类型的函数调用初始化node->value因为这会限制这些Node函数的灵活性,它只是设置为0以确保它没有填充垃圾。 并且因为调用者编写node->value = ...实际上是微不足道的。

现在你可以做到这两点。

int main(void) {
    Node my_node;
    Node_init(my_node);

    Node *your_node = Node_new();

    // Use them as you like

    Node_free(your_node);
}

然而! 谁控制内存的问题可能导致问题,如此连接的树结构。 如果你想破坏整个树,你将不得不抓取它并销毁所有节点。 但是,如果您不知道谁拥有节点的内存,那么清理将很困难。

因此,对于树或图形,您应该通过仅允许一个内存模式来简化操作。 堆内存更通用,特别是在大型程序中,所以我会选择它。

如果您有已知数量的节点,则不需要链接列表。 这意味着您需要在实际场景中动态分配节点。 因此,第一种方法需要较少的样板代码。

Node* node = new_node();
if (node == NULL)
   goto error;

但是,这种方法迫使一个人使用malloc而不是另一个分配器。 [1]第二种方法提供了更大的灵活性[2] ,所需的额外代码相当小。 采用第二种方法使图书馆更具可重用性。

Node* node = malloc(sizeof(Node));
if (node == NULL)
   goto error;

init_node(node);

我认为第三种方法没有任何优势。


  1. 例如,Perl扩展应该使用Perl的内存分配器而不是malloc 例如,静态分配的内存优于嵌入式系统中动态分配的内存。

  2. 它可以与静态和动态分配的结构一起使用,您可以使用除malloc之外的分配器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM