c - malloc-free-malloc 和严格别名

标签 c strict-aliasing

我最近一直在尝试理解严格别名的一个特定方面,我认为我已经制作了尽可能最小的有趣代码。 (对我来说很有趣,就是这样!)

更新:根据到目前为止的答案,很明显我需要澄清这个问题。从某个角度来看,这里的第一个列表是“明显”定义的行为。真正的问题是在自定义分配器和自定义内存池中遵循此逻辑。如果我 malloc开头一大块内存,然后自己写my_mallocmy_free使用那个单一的大块,然后是UB,理由是它不使用官方free ?

我会坚持使用 C,有点武断。我的印象是更容易谈论,C 标准更清晰一些。

int main() {
    uint32_t *p32 = malloc(4);
    *p32 = 0;
    free(p32);

    uint16_t *p16 = malloc(4);
    p16[0] = 7;
    p16[1] = 7;
    free(p16);
}

可能是第二个 malloc将返回与第一个 malloc 相同的地址(因为它是 free d 之间)。这意味着它使用两种不同的类型访问相同的内存,这违反了严格的别名。那么以上肯定是未定义的行为(UB)吗?

(为简单起见,我们假设 malloc 总是成功。我可以添加对 malloc 返回值的检查,但这只会使问题变得困惑)

如果不是UB,为什么?标准中是否有明确的异常(exception),即 mallocfree (和 calloc/realloc/...)是否允许“删除”与特定地址关联的类型,允许进一步访问以在地址上“印记”新类型?

malloc/free是特殊的,那么这是否意味着我不能合法地编写自己的分配器来克隆 malloc 的行为? ?我确信有很多项目都带有自定义分配器——它们都是 UB 吗?

自定义分配器

因此,如果我们决定这样的自定义分配器必须是定义行为,那么这意味着严格的别名规则本质上是“不正确的”。我会更新它说只要您不再使用旧类型的指针,就可以通过不同("new")类型的指针来写入(而不是读取)。如果确认所有编译器基本上都遵守了这个新规则,那么这个措辞可能会悄悄地改变。

我的印象是gccclang基本上尊重我的(激进的)重新解释。如果是这样,也许应该相应地编辑标准?我关于 gcc 的“证据”和 clang很难描述,它使用 memmove具有相同的源和目标(因此被优化掉)以这样一种方式阻止任何不需要的优化,因为它告诉编译器将来通过目标指针读取的内容将为之前通过源指针写入的位模式别名。我能够相应地阻止不受欢迎的解释。但我想这并不是真正的“证据”,也许我只是幸运。 UB 显然意味着编译器也允许给我误导性的结果!

( ...当然,除非有另一条规则使 memcpymemmove 变得特殊,就像 malloc 可能是特殊的一样。允许它们将类型更改为目标指针的类型。这与我的“证据”一致。)

反正我是跑题了。我想一个非常简短的答案是:“是的,malloc(和 friend )是特殊的。自定义分配器并不特殊,因此是 UB,除非它们为每种类型维护单独的内存池。此外,请参见示例 X一段极端的代码,其中编译器 Y 做了不受欢迎的事情,正是因为编译器 Y 在这方面非常严格,并且与这种重新解释相矛盾。”

跟进:非malloc怎么办? ed内存?是否同样适用。 (局部变量,静态变量,...)

最佳答案

以下是 C99 严格的别名规则(我希望是)它们的全部内容:

6.5
(6) The effective type of an object for an access to its stored value is the declared type of the object, if any. If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

(7) An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type.



这两个子句共同禁止一种特定情况,即通过 X 类型的左值存储值,然后通过与 X 不兼容的 Y 类型的左值检索值。

因此,当我阅读标准时,即使这种用法也完全可以(假设 4 个字节足以存储一个 uint32_t 或两个 uint16_t )。
int main() {
    uint32_t *p32 = malloc(4);
    *p32 = 0;
    /* do not do this: free(p32); */

    /* do not do this: uint16_t *p16 = malloc(4); */
    /* do this instead: */
    uint16_t *p16 = (uint16_t *)p32;

    p16[0] = 7;
    p16[1] = 7;
    free(p16);
}

没有规则禁止存储 uint32_t然后随后存储一个 uint16_t在同一个地址,所以我们完全没问题。

因此,没有什么可以禁止编写完全兼容的池分配器。

关于c - malloc-free-malloc 和严格别名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31534716/

相关文章:

c - scanf AFTER fgets 破坏了 fgets 语句

c - C语言中的TCP校验和

比较opencv中灰度图像的直方图

c++ - 严格的别名规则

c++ - 变量中内存的简单重叠是否违反了别名规则?

c - C语言十进制转二进制

C冲突类型错误

c - 在动态 bool 数组上使用 memset 是否定义明确?

c - 在不违反 C99 中严格的别名规则的情况下使用 void * 输入双关语

c - 通过展开继承是否违反严格的别名规则?