malloc 如何使用严格的别名 - 它只能在单个编译单元内被违反吗?
How does malloc work with strict aliasing - can it only be violated within a single compilation unit?
阅读 , I have a similar question like this one 后,想知道内存分配器如何在不违反严格的别名规则的情况下工作。但我不是想知道 re-using 释放的内存,我想知道如何在不违反严格别名的情况下将分配的 objects 定位在线性内存中。
到目前为止我看过的所有堆内存分配器都将它们的内存分成某种块,前面有一个 header。但是,malloc returns a void *
并且通常指向 header 之后的内存。这是一个非常缩小的示例来说明这一点。
#include <stddef.h>
struct block_header {
size_t size;
};
struct block_header *request_space(size_t size);
void *malloc(size_t size) {
struct block_header *block = request_space(size);
// I guess this violates strict aliasing, because the caller will
// convert the pointer to something other than struct block_header?
// Or why wouldn't it?
return block + 1;
}
我已经研究了一段时间了,但我看不出分配器如何在不违反严格别名的情况下将其指针定位在内存区域中。我错过了什么?
malloc
的源代码不需要像普通源代码那样符合C标准。它是 C 实现的一部分。
从事 malloc
、编译器和 C 实现的其他部分的人员有责任确保他们协同工作。这可以包括编译器特殊对待 malloc
和 malloc
使用 C 编译器保证但不是 C 标准保证的行为。
C 标准有意避免要求所有实现都适用于所有目的。相反,它旨在允许用于各种目的的实现,以 "conforming language extension" 的形式,以对这些目的有用的方式有意义地处理构造,即使标准没有强加任何要求。因此,该标准允许用于需要手动内存管理的任务的实现支持 "popular extensions" 以促进此类任务,即使它有意避免要求不用于此类任务的实现提供此类支持。
许多类型的内存分配器在其语义仅限于标准规定的实现上实现是不切实际的。然而,支持委员会描述为 "Don't prevent the programmer from doing what needs to be done" 的 C 精神原则的实现,以及被设计和配置为适合构建此类分配器的实现,将识别存储将被用作不止一种类型的迹象。编译器识别此类指示的确切情况范围作为 "quality of implementation" 问题超出了标准的管辖范围。从实际的角度来看,clang 和 gcc 的作者选择以最低允许质量的方式行事,但使用 -fno-strict-aliasing
时除外,但这意味着想要使用这些 "interesting" 做任何事情的程序员编译器必须使用该选项。没有任何证据表明该标准的作者希望程序员克服困难以适应低质量实现的局限性;相反,他们希望市场比委员会更能判断编译器应该如何最有效地完成各种任务。
根据标准,这些东西从不违反严格的别名:
- 正在投射指针。
- 做指针运算。
- 写入 malloc 的 space。
你在 malloc'd space 中不允许做的事情是 读取 一些内存作为不同于它被写入的类型(列表除外当然是允许的别名类型)。
规则的文本在 C11 6.5/7 中:
An object shall have its stored value accessed only by [...]
并且 6.5/6 中的文本解释说,如果我们在 malloc 中 space,那么写入会将写入的类型印在目标上(因此不会存在类型不匹配)。
到目前为止您发布的代码从未执行过禁止的操作,因此没有明显的问题。如果有人使用你的分配器然后读取内存而不写入它,只会出现问题。
脚注 1: 根据 committee response to DR236 6.5/6 显然是有缺陷的,但从未修复,所以谁知道它会留给我们什么。
脚注 2: 正如埃里克指出的那样,该标准不适用于实现内部,但请考虑我在其他问题中对某些用户编写的分配器的评论你链接到.
阅读
到目前为止我看过的所有堆内存分配器都将它们的内存分成某种块,前面有一个 header。但是,malloc returns a void *
并且通常指向 header 之后的内存。这是一个非常缩小的示例来说明这一点。
#include <stddef.h>
struct block_header {
size_t size;
};
struct block_header *request_space(size_t size);
void *malloc(size_t size) {
struct block_header *block = request_space(size);
// I guess this violates strict aliasing, because the caller will
// convert the pointer to something other than struct block_header?
// Or why wouldn't it?
return block + 1;
}
我已经研究了一段时间了,但我看不出分配器如何在不违反严格别名的情况下将其指针定位在内存区域中。我错过了什么?
malloc
的源代码不需要像普通源代码那样符合C标准。它是 C 实现的一部分。
从事 malloc
、编译器和 C 实现的其他部分的人员有责任确保他们协同工作。这可以包括编译器特殊对待 malloc
和 malloc
使用 C 编译器保证但不是 C 标准保证的行为。
C 标准有意避免要求所有实现都适用于所有目的。相反,它旨在允许用于各种目的的实现,以 "conforming language extension" 的形式,以对这些目的有用的方式有意义地处理构造,即使标准没有强加任何要求。因此,该标准允许用于需要手动内存管理的任务的实现支持 "popular extensions" 以促进此类任务,即使它有意避免要求不用于此类任务的实现提供此类支持。
许多类型的内存分配器在其语义仅限于标准规定的实现上实现是不切实际的。然而,支持委员会描述为 "Don't prevent the programmer from doing what needs to be done" 的 C 精神原则的实现,以及被设计和配置为适合构建此类分配器的实现,将识别存储将被用作不止一种类型的迹象。编译器识别此类指示的确切情况范围作为 "quality of implementation" 问题超出了标准的管辖范围。从实际的角度来看,clang 和 gcc 的作者选择以最低允许质量的方式行事,但使用 -fno-strict-aliasing
时除外,但这意味着想要使用这些 "interesting" 做任何事情的程序员编译器必须使用该选项。没有任何证据表明该标准的作者希望程序员克服困难以适应低质量实现的局限性;相反,他们希望市场比委员会更能判断编译器应该如何最有效地完成各种任务。
根据标准,这些东西从不违反严格的别名:
- 正在投射指针。
- 做指针运算。
- 写入 malloc 的 space。
你在 malloc'd space 中不允许做的事情是 读取 一些内存作为不同于它被写入的类型(列表除外当然是允许的别名类型)。
规则的文本在 C11 6.5/7 中:
An object shall have its stored value accessed only by [...]
并且 6.5/6 中的文本解释说,如果我们在 malloc 中 space,那么写入会将写入的类型印在目标上(因此不会存在类型不匹配)。
到目前为止您发布的代码从未执行过禁止的操作,因此没有明显的问题。如果有人使用你的分配器然后读取内存而不写入它,只会出现问题。
脚注 1: 根据 committee response to DR236 6.5/6 显然是有缺陷的,但从未修复,所以谁知道它会留给我们什么。
脚注 2: 正如埃里克指出的那样,该标准不适用于实现内部,但请考虑我在其他问题中对某些用户编写的分配器的评论你链接到.