c - 类似 malloc 函数的严格别名的原因

标签 c strict-aliasing

AFAIK,在三种情况下别名是可以的

  1. 仅限定符或符号不同的类型可以互为别名。
  2. struct 或 union 类型可以为包含在其中的类型设置别名。
  3. 将 T* 转换为 char* 是可以的。 (不允许相反)

阅读来自 John Regehrs blog posts 的简单示例时,这些是有意义的但我不确定如何推断更大示例的别名正确性,例如类似 malloc 的内存安排。

我正在阅读 Per Vognsens re-implementation肖恩·巴雷特 stretchy buffers .它使用类似 malloc 的模式,其中缓冲区在它之前具有关联的元数据。

typedef struct BufHdr {
    size_t len;
    size_t cap;
    char buf[];
} BufHdr;

通过从指针 b 中减去偏移量来访问元数据:

#define buf__hdr(b) ((BufHdr *)((char *)(b) - offsetof(BufHdr, buf)))

这是原始 buf__grow 函数的简化版本,它扩展了缓冲区并将 buf 作为 void* 返回。

void *buf__grow(const void *buf, size_t new_size) { 
     // ...  
     BufHdr *new_hdr;  // (1)
     if (buf) {
         new_hdr = xrealloc(buf__hdr(buf), new_size);
     } else {
         new_hdr = xmalloc(new_size);
         new_hdr->len = 0;
     }
     new_hdr->cap = new_cap;
     return new_hdr->buf;
}

用法示例(buf__grow 隐藏在宏后面,但这里为了清楚起见它是公开的):

int *ip = NULL;
ip = buf__grow(ip, 16);
ip = buf__grow(ip, 32);

在这些调用之后,我们在堆上有 32 + sizeof(BufHdr) 字节的大内存区域。我们有 ip 指向那个区域,我们有 new_hdrbuf__hdr 在执行的不同点指向它。

问题

这里是否存在严格的别名违规? AFAICT、ip 和一些 BufHdr 类型的变量不应被允许指向同一内存。

或者是因为 buf__hdr 没有创建左值意味着它没有为与 ip 相同的内存别名? new_hdr 包含在 buf__grow 中,其中 ip 不是“实时”的,这意味着它们也不是别名吗?

如果 new_hdr 在全局范围内,那会改变一切吗?

C 编译器是跟踪存储类型还是只跟踪变量类型?如果有存储,比如在buf__grow中分配的内存区域没有任何变量指向它,那么该存储的类型是什么?只要没有与该内存关联的变量,我们是否可以自由地重新解释该存储?

最佳答案

标准没有定义任何方式,通过这种方式可以使用一种类型的左值来派生可用于访问存储的第二种类型的左值,除非后者具有字符类型。甚至像这样基本的东西:

union foo { int x; float y;} u = {0};
u.x = 1;

调用 UB,因为它使用 int 类型的左值来访问与 union foofloat 类型的对象关联的存储。另一方面,该标准的作者可能认为,由于没有编译器编写者会笨到使用左值类型规则作为不以有用方式处理上述内容的理由,因此没有必要尝试制定明确的规则要求他们这样做。

如果编译器保证不“执行”规则,除非是以下情况:

  1. 在函数或循环的特定执行期间修改对象;
  2. 两种或多种不同类型的左值用于在此类执行期间访问存储;和
  3. 在这种执行过程中,没有一个左值明显地主动从另一个派生

这样的保证足以使 malloc() 实现不会出现与“别名”相关的问题。虽然我怀疑标准的作者可能希望编译器编写者自然地支持这样的保证,无论它是否被强制要求,但 gcc 和 clang 都不会这样做,除非使用 -fno-strict-aliasing 标志.

不幸的是,当在缺陷报告 #028 中被要求澄清 C89 规则的含义时,委员会的回应是建议通过取消引用指向 union 成员的指针形成的左值将主要表现得像直接使用成员访问形成的左值运算符,除了如果直接在 union 成员上完成将调用实现定义的行为的操作如果在指针上完成则应调用 UB。在编写 C99 时,委员会决定通过将该原则编入 C99 的“有效类型”规则来“澄清”事情,而不是承认派生类型的左值可用于访问父对象的任何情况[有效的遗漏类型规则无法纠正!]。

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

相关文章:

c - 在 C 中使用 'else' 的预期标识符

c - LuaJIT FFI 不从可执行文件加载符号

c - 为 char 数组分配大小时出现段错误

c++ - 符合标准的编译器应该能够优化这些指针比较中的哪一个 "always false"?

c - 将 64 位整数相除,就像被除数左移 64 位一样,无需 128 位类型

c - 我是否通过创建虚拟结构数据类型违反了严格的别名规则?

c++ - `std::complex<T>[n]` 和 `T[n*2]` 类型别名

c++ - 将基本类型数组中的内存重用于不同(但仍然是基本)类型数组是否合法

c++ - 从字节初始化一个由 trivially_copyable 但不是 default_constructible 对象组成的数组。 [intro.object] 中的混淆

c - 无法使用c中的结构设置名称(字符数组)