这个 geeksforgeeks trie 实现是否存在内存泄漏问题?
Is this geeksforgeeks trie implementation having a memory leak problem?
我正在读一个geeksforgeeks_implementation_of_trie,我发现new
创建的TrieNode
个对象没有删除,在main函数中,有一个未删除的指针root
:
struct TrieNode *root = getNode();
并且在函数getNode()
中也有未删除的指针pNode
,是否有内存泄漏?是否应该有一个函数负责销毁由指针组成的trie树?
struct TrieNode *getNode(void)
{
struct TrieNode *pNode = new TrieNode;
pNode->isEndOfWord = false;
for (int i = 0; i < ALPHABET_SIZE; i++)
pNode->children[i] = NULL;
return pNode;
}
这个函数隐含了一个合约,函数的调用者负责删除分配的TrieNode
。如果调用方不遵守此合同,则只会出现内存泄漏。
既然你说这个 TrieNode
在 main
的任何地方都没有被删除,可能有泄漏。除非你能找到这个结构被删除的地方,否则就会有泄漏。这就是为什么 RAII 是一个如此强大的概念。如果有一个Trie
对象包含了所有的TrieNode
并且负责节点的分配和删除,那么你就完全不用担心泄漏了。
让调用者负责管理分配的资源是危险的。别这样。
你可以争辩说这个特定的实现不一定是泄漏,如果程序足够简单,它所做的只是获取 TrieNode
s,用它们做事,然后退出。在这种情况下,内存将在程序退出时释放到 OS。但这是一个语义论点,提供这样做的示例代码是不好的做法,并且可能导致货物崇拜程序员做这种不好的做法。
漏还是不漏...
根据 Wikipedia,当计算机程序以不释放不再需要的内存的方式错误地管理内存分配时,就会发生泄漏 :
getNode()
的作用是return一个指向已初始化的TrieNode
的指针。所以它本身并没有泄漏任何东西:我们可以假设根据它的目的,当函数return是它的指针时仍然需要相应的对象。
调用上下文确实会泄漏内存:正如您正确指出的那样,它创建了一个带有 getNode()
的节点并且从不删除该节点。这是不好的做法。
但是你必须在它的上下文中替换这段代码:这篇文章是一个教程。它建议删除为 additional exercise.
为什么他们在教程中不显示删除
因为删除TrieNode
不是一件容易的事情,如果你必须小心的话:删除一个节点还需要找到所有应该删除的引用节点。由于 trie 是一种图,因此多个节点可以指向同一目的地。所以你不能盲目地删除这些节点,如果你不想因为悬挂指针和双重删除而冒 UB 的风险。您需要实施标记算法或某种引用计数
但不要按照本教程进行操作
这个 TrieNode
实现非常糟糕:在现代 C++ 中:
- 你会让构造函数初始化一个
TrieNode
,无论它是动态创建的节点还是本地节点。
- 你会使用智能指针而不是原始指针。由于一个节点本质上可以被多次引用,所以你会选择
shared_ptr
- 您可能更愿意使用
vector<shared_ptr<TrieNode>>
甚至 map<char, shared_ptr<TrieNode>>
而不是旧式数组。
但我还是会让你把删除作为练习;-)
我正在读一个geeksforgeeks_implementation_of_trie,我发现new
创建的TrieNode
个对象没有删除,在main函数中,有一个未删除的指针root
:
struct TrieNode *root = getNode();
并且在函数getNode()
中也有未删除的指针pNode
,是否有内存泄漏?是否应该有一个函数负责销毁由指针组成的trie树?
struct TrieNode *getNode(void)
{
struct TrieNode *pNode = new TrieNode;
pNode->isEndOfWord = false;
for (int i = 0; i < ALPHABET_SIZE; i++)
pNode->children[i] = NULL;
return pNode;
}
这个函数隐含了一个合约,函数的调用者负责删除分配的TrieNode
。如果调用方不遵守此合同,则只会出现内存泄漏。
既然你说这个 TrieNode
在 main
的任何地方都没有被删除,可能有泄漏。除非你能找到这个结构被删除的地方,否则就会有泄漏。这就是为什么 RAII 是一个如此强大的概念。如果有一个Trie
对象包含了所有的TrieNode
并且负责节点的分配和删除,那么你就完全不用担心泄漏了。
让调用者负责管理分配的资源是危险的。别这样。
你可以争辩说这个特定的实现不一定是泄漏,如果程序足够简单,它所做的只是获取 TrieNode
s,用它们做事,然后退出。在这种情况下,内存将在程序退出时释放到 OS。但这是一个语义论点,提供这样做的示例代码是不好的做法,并且可能导致货物崇拜程序员做这种不好的做法。
漏还是不漏...
根据 Wikipedia,当计算机程序以不释放不再需要的内存的方式错误地管理内存分配时,就会发生泄漏 :
getNode()
的作用是return一个指向已初始化的TrieNode
的指针。所以它本身并没有泄漏任何东西:我们可以假设根据它的目的,当函数return是它的指针时仍然需要相应的对象。调用上下文确实会泄漏内存:正如您正确指出的那样,它创建了一个带有
getNode()
的节点并且从不删除该节点。这是不好的做法。
但是你必须在它的上下文中替换这段代码:这篇文章是一个教程。它建议删除为 additional exercise.
为什么他们在教程中不显示删除
因为删除TrieNode
不是一件容易的事情,如果你必须小心的话:删除一个节点还需要找到所有应该删除的引用节点。由于 trie 是一种图,因此多个节点可以指向同一目的地。所以你不能盲目地删除这些节点,如果你不想因为悬挂指针和双重删除而冒 UB 的风险。您需要实施标记算法或某种引用计数
但不要按照本教程进行操作
这个 TrieNode
实现非常糟糕:在现代 C++ 中:
- 你会让构造函数初始化一个
TrieNode
,无论它是动态创建的节点还是本地节点。 - 你会使用智能指针而不是原始指针。由于一个节点本质上可以被多次引用,所以你会选择
shared_ptr
- 您可能更愿意使用
vector<shared_ptr<TrieNode>>
甚至map<char, shared_ptr<TrieNode>>
而不是旧式数组。
但我还是会让你把删除作为练习;-)