是免费的(a_comparable_pointer)`定义良好还是UB?

时间:2016-09-19 21:40:35

标签: c language-lawyer

当然,分配和释放相同的指针是明确定义的......

void *p1 = malloc(42);
assert(p1);
...
free(p1);

...并且当存在这些整数类型时,通过intptr_t/uintptr_t转换会创建可比指针(如“比较相等”C11 7.20.1.4),但不一定是相同的位模式。可以说p2p3具有相同的,但可能有不同的表示形式。

void *p2 = malloc(42);
assert(p2);
uintptr_t u2 = (uintptr_t) p2;
...
void *p3 = (void *) u2;

// Specified to evaluate true C11 7.20.1.4
if (p2 == p3) ...

// Maybe true, maybe false
if (memcmp(&p2, &p3, sizeof p2) == 0) ...

// Note - early version of post had this errant code
// if (memset(&p2, &p3, sizeof p2) == 0) ...

现在通过可比较的指针到free()

  

free函数导致ptr指向的空间被释放,...如果参数不是匹配,则先前由内存管理函数返回的指针......行为未定义。 C11dr§7.22.3.33

void *p4 = malloc(42);
assert(p4);
uintptr_t u4 = (uintptr_t) p4;
...
void *p5 = (void *) u4;
...
free(p5);  // UB or not UB?

因此标题问题似乎已经完成:
“可比”是否足以“匹配”free()

我怀疑free(p5)undefined behavior(UB),但我不确定。没有特别的应用 - 只是试图理解C的角落,不要急。

5 个答案:

答案 0 :(得分:3)

类型uintptr_t保证转换为此类型并返回后的void指针将与原始指针 1 进行比较。

如果指针比较等于另一个指针,则它们指向同一个对象 2

因此定义了最后一个示例中的行为,您可以使用该指针释放该对象:free(p5)

1 (引用自:ISO / IEC 9899:201x 7.20.1.4能够保持指针1的整数类型)
以下类型指定无符号整数类型,其属性为any 指向void的指针可以转换为这种类型,然后转换回指向void的指针, 并且结果将与原始指针进行比较: uintptr_t的

2 (引自:ISO / IEC 9899:201x 6.5.9平等操作员6)
两个指针比较相等,当且仅当两个指针都是空指针时,两个指针都指向 相同的对象(包括指向对象和开头的子对象的指针)或函数, 两者都是指向同一个数组对象的最后一个元素之后的指针,或者一个是指针 到一个数组对象的末尾,另一个是指向另一个数组对象的开头的指针 紧接着地址中第一个数组对象的数组对象 空间。 109)

答案 1 :(得分:3)

参考文献N1570,即2011年ISO C标准(C11)的最新公开草案。

void *p4 = malloc(42);
assert(p4);
uintptr_t u4 = (uintptr_t) p4;
...
void *p5 = (void *) u4;
...
free(p5);  // UB or not UB?

uintptr_t的定义保证指针值p4p5相等;更简洁,p4 == p5。根据{{​​1}}对6.5.9p6中指针的定义,我们知道==p4指向同一个对象(因为我们已经确定了{{p5的值1}}不是空指针。)

这并不能保证它们具有相同的表示形式。标准对指针的表示几乎没有说明(除了它们有一个表示),因此p3p4完全有可能有不同的表示。该标准在6.2.6.1p4中明确允许这一点:

  

具有相同对象表示的两个值(除NaN之外)   比较相等,但比较相等的值可能有不同的对象   表示。

(实际上可能会或可能不会,具体取决于实施方式。)

现在问题是,究竟是什么传递给p5

6.5.2.2中描述了函数调用。第4段说:

  

在准备调用函数时,会对参数进行求值,   并为每个参数分配相应的   参数。

(强调补充。)

所以free看到的不是free()的对象表示(可能与p5的对象表示不同),而是p4 {1}},保证与p5的值相同。传递该值的机制可能涉及制作对象表示的副本,但所有标准都表示该值是传递的,p4必须处理该值的任何不同表示。同样的价值也是如此。

答案 2 :(得分:2)

有关转换为uintptr_t的语言使用“比较相等”,但请注意void*T*之间的转换使用相同的词组:

  

指向void的指针可以转换为指向任何不完整或对象类型的指针。指向任何不完整或对象类型的指针可以转换为指向void并再次返回的指针;结果应该等于原始指针。

(ISO C99标准第6.3.2.3/1节)

如果匹配比较等于不是同义词,那么根据您的推理,以下也将是UB:

T* p = malloc(n);
...
free(p);

而是需要:

void* p0 = malloc(n);
T* p = p0;
...
free(p0);

这与常识不符。此外,标准和K& R第2版中malloc使用的示例不做任何此类操作。

答案 3 :(得分:1)

free不知道/关心返回值的存储位置,或者以后转换为什么类型(它被转换回void*)。因此,只要它指向已分配且尚未释放的内存,则传入的指针无关紧要。

答案 4 :(得分:0)

通常,只要指向有效的堆块,任何对free的调用都将没有问题。当参数没有指向有效堆块的开头时,自由行为变得不可预测。例如,下面的代码将编译,但在执行期间会崩溃,因为修改后的指针不指向有效的堆块:

int* pn = (int*)malloc(sizeof(int));
pn += 2;                // Now points beyond the beginning of an allocated heap block
void* pv = (void*)pn;   
free(pv);               // Therefore will crash

如果删除pn + = 2,它将再次正常。 底线:可以修改和转换你的指针,但是当你释放它们时,你应该确保它们指向实际分配的saomething并且还没有被释放。