【问题标题】:Does assignment change the effective type of a variable?赋值会改变变量的有效类型吗?
【发布时间】:2016-12-22 20:20:28
【问题描述】:

可能相关的309702517687082

我正在考虑编写一个内存分配器,并试图找出如何解决现代 C 对类型双关和别名的限制。只要分配器底层的缓冲区最初是从 malloc 检索的,因为 malloc 的指针没有声明的类型,我想我是清楚的。

过度对齐的字符缓冲区确实具有声明的类型。我不认为我可以将指针转换为任意类型,并且必须通过 char 指针仔细写入它,例如使用 memcpy。这很痛苦,因为我看不到通过 memcpy hack 向调用者隐藏写入的方法。

考虑以下几点:

#include <assert.h>
#include <stdalign.h>
#include <stdint.h>
#include <string.h>

static_assert(sizeof(double) == sizeof(uint64_t), "");
static_assert(alignof(double) == alignof(uint64_t), "");

int main(void)
{
  alignas(alignof(double)) char buffer[sizeof(double)];
  // effective type of buffer is char [8]                                                                                                                                                                                                 

  {
    double x = 3.14;
    memcpy(&buffer, &x, sizeof(x));
    // effective type of buffer is now double                                                                                                                                                                                             
  }

  {
    uint64_t* ptr = (uint64_t*)&buffer;
    // effective type of buffer is still double                                                                                                                                                                                           
    // reading from *ptr would be undefined behaviour                                                                                                                                                                                     
    uint64_t y = 42;
    memcpy(ptr, &y, sizeof(y));
    // effective type of buffer is now uint64_t                                                                                                                                                                                           
  }

  {
    double* ptr = (double*)&buffer;
    // effective type of buffer is still uint64_t                                                                                                                                                                                         
    uint64_t retrieve = *(uint64_t*)ptr;  // OK                                                                                                                                                                                           
    assert(retrieve == 42);

    double one = 1.0;
    *ptr = one;  // Unsure if OK to dereference pointer of wrong type                                                                                                                                                                     
    // What is the effective type of buffer now?                                                                                                                                                                                          
    assert(*ptr == one);
  }
}

这是可行的,因为我可以努力确保每次自定义分配器返回一个使用 memcpy 写入的 void 指针,而不是强制转换为所需的类型。也就是替换

double * x = my_malloc(sizeof(double));
*x = 3.14;

与:

double tmp = 3.14;
void * y = my_malloc(sizeof(double));
memcpy(y, &tmp, sizeof(double));
double * x = (double*)y;

所有这些线路噪音都被编译器中的优化通道消除了,但看起来确实很傻。是否必须符合标准?

这绝对可以通过在 asm 中而不是在 C 中编写分配器来解决,但我并不是特别热衷于这样做。如果问题未指定,请告诉我。

【问题讨论】:

    标签: casting c11


    【解决方案1】:

    不,一般不会。它只改变分配时没有类型的对象的有效类型,即通过malloc和朋友分配的对象。

    因此,如果您执行编译器和库实现的用户 之类的操作,则程序的行为是未定义的。分配为char[] 的数组始终具有该类型的有效类型。

    如果您是编译器或库编写者,则不受这些限制的约束。您只需要说服您的工具链不要过多地优化事物。通常,您可以通过确保您的分配器函数存在于它自己的仅导出void* 的 TU 中来做到这一点,并确保您没有打开链接时间优化或类似的东西。

    如果您提供这样一个函数作为 C 库的一部分(替换),那么您作为实现者必须向您的用户提供保证。

    【讨论】:

    • 谢谢。这基本上就是我所害怕的。静态链接启用 LTO 的 libc 可能是个坏消息。对于应用程序级分配器来说绝对是个坏消息。我越来越确信简化 TBAA 不值得将明确的开发者意图转化为 UB。
    • @JonChesterfield:我强烈怀疑 C89 的作者打算让编译器编写者将其解释为编译器不必悲观地假设可能会出现别名 在代码中没有任何暗示的情况下,但并不是说质量编译器应该在别名明显的情况下忽略别名,尤其是在可能有用的平台上。给定uint32_t float_as_bits(float *fp) { return *(uint32_t*)fp; },编译器假设函数可能访问float 类型的东西真的是“悲观”吗?
    • @supercat 也许吧。这当然是一件麻烦事。不幸的是,这就是 C++ 的发展方向,而 C 似乎也在追随。从好的方面来说,直接生成机器代码仍然是一种选择。
    • @JonChesterfield:早在 1990 年代初期,我认为 C 代码需要使用 memcpy 来复制字节计数数组而不是正确的数组复制函数是很愚蠢的。我从来没有想到,未来的 C 编译器会朝着更频繁地需要 memcpy 的方向发展。我还发现编译器编写者的态度很奇怪,即程序员如果想要控制就应该用汇编语言编写,而发明 C 的主要目的之一是允许在没有汇编语言的情况下进行低级编程
    猜你喜欢
    • 1970-01-01
    • 2021-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 2020-04-19
    相关资源
    最近更新 更多