1

作为 C 编程语言项目的一部分,我一直在处理列表。仅供参考,条目和列表的结构是非常基本的数据结构,因此定义为

typedef struct entry entry_t;
typedef struct list list_t;

struct entry
{
    void * data;
    entry_t * next;
};

struct list
{
    size_t size;
    entry_t * head;
    entry_t * tail;
};

headtail分别指向第一个和最后一个条目。只是没有像标题这样的开销条目,而是列表中的所有数据条目,因此如果列表中只有一个条目,head并且tail应该指向相同的条目。此外,还有这样一个片段可以删除列表中的所有条目

list_t list;
entry_t * current, * next;
for(
    current=list->head,
    next=current->next,
    free(current);
    current!=list->tail;
    current=next,
    next=current->next,
    free(current)
);

问题是内存地址currentlist->tail指向的值之间的比较是在current释放指针后评估的。假设current并且list->tail现在指向同一个内存块然后current被释放,评估的结果是什么,更重要的是,根据您的经验,结果(无论是什么)在所有不同的编译器中都是确定性的吗?就我而言,该程序在 MSVC 中编译并正确运行,这表明内存地址currentlist->tail指向的值在current释放后相等。

4

4 回答 4

2

在这一点上并不重要,但如果你正在清除列表,那么请原谅显而易见的,但谁在乎尾巴(或大小)是什么。无论如何,它们即将失效。为什么不简单:

// assuming list is a valid
list_t* list;

while (list->head)
{
    entry_t *tmp = list->head;
    list->head = list->head->next;
    free(tmp);
}
list->head = list->tail = NULL;
list->size = 0;
于 2012-11-20T12:54:18.513 回答
1

从技术上讲,你违反了规则。由 6.2.4 (2)

对象的生命周期是程序执行期间保证为其保留存储的部分。一个对象存在,有一个不变的地址,并在其整个生命周期中保留其最后存储的值。如果对象在其生命周期之外被引用,则行为未定义。当指针指向(或刚刚过去)的对象到达其生命周期的末尾时,指针的值变得不确定。

在 C2011 标准的 n1570 草案中(在 C99 标准的同一个地方几乎相同),在freeing之后current它的值变得不确定(如果current碰巧指向与list->tail之前相同的对象free,那么 的值也list->tail变得不确定)。因此在比较中使用它会调用未定义的行为。

在跟踪指针有效性并检查每次比较等的假设实现中,它可能会崩溃或做其他有趣的事情。

但是,在实践中,我不希望任何实现能够做到这一点,所以它几乎肯定会产生预期的结果。

正如WhozCraig 指出的那样,实际上有一个平台进行跟踪。但是,这会将所有无效指针视为NULL,因此,如果我理解正确的话,即使有问题的代码也会产生预期的结果。

于 2012-11-20T13:02:59.477 回答
0

评估的结果是什么

C 标准说结果是未定义的。

更重要的是,根据您的经验,结果(无论是什么)在所有不同的编译器中都是确定性的

内存一旦被进程分配,通常在进程终止之前不会返回给操作系统。我所知道的所有编译器都将评估current!=list->tail为 false 而不会引发异常。

于 2012-11-20T12:59:06.053 回答
0

这是for loop实现

entry_t *current,*prev,*next;  

  for(
        current = list->head;

        current != NULL;

        prev=current,
        current = current->next,
        free(prev),
        prev=NULL
    );
于 2012-11-20T13:03:26.937 回答