将void *缓冲区作为char *之外的其他内容使用是否合法?

时间:2017-04-30 04:57:20

标签: c io language-lawyer strict-aliasing

假设我的某个外部源的已知大小为void *buffer(例如,它可能是fread() C API [1],或者是{ {1}}致电)。

我可以有效地将此mmap投射到哪些类型的指针,然后从中读取?

如果我知道这些数据是由16位值组成的,那么是否允许将void *转换为void *并直接通过取消引用指针来读取值?

我知道当然存在可能的字节序问题,但首先这样做是否合法(例如,对齐方式)?

如果以这种方式强制转换整个缓冲区是合法的,那么缓冲区的一部分呢?例如,如果我知道前64个字节是uint16_t *,那么接下来的10,000个字节是char *数据?

[1]在uint16_t的情况下,假设内存分配有fread()

2 个答案:

答案 0 :(得分:2)

有两个可能的问题:

  1. 演员表可能会受到对齐限制。
  2. 阅读或撰写演员表的结果须遵守严格的别名规则。
  3. 对于第1部分,它是实现 - 定义平台是否具有对齐要求。请参阅编译器文档,并且必须说明是否存在此类限制。如果他们这样做,那么如果您投射的指针未正确对齐投射目标指向的类型,则它是未定义的行为。

    对于第2部分,您需要了解严格别名规则。 See this thread用于标准报价加上各种形式的介绍。

    我在这里的回答仅指在动态分配的空间中工作。如果通过不同类型读取和写入数据会出现问题,其中不允许执行读取的类型为写入的类型添加别名:

    uint16_t *buf = malloc(50);
    ((char *)buf)[0] = 'a';
    ((char *)buf)[1] = 'b';
    *buf;  // undefined behaviour
    

    所以要回答你的问题,你需要知道数据是如何写的。

    fread的情况下,标准(C11 7.21.8.1/2)指定它写入,好像有unsigned char个字符的一系列赋值。因此,fread进入malloc缓冲区然后通过uint16_t表达式进行读取将是未定义的行为。

    mmap功能不属于C标准。因此,该标准不包括如果您在写入mmap'空格之前读出会发生什么。但我会说,如果你写入这样的空间,然后从同一地址读取,那么严格的别名规则将适用。

    某些编译器具有“禁用严格别名”的开关或编译指示,这意味着它们将编译代码,就好像所有别名都被允许一样。如果您想使用违反严格别名规则的编码技术,那么最好将这些开关用于该代码。

答案 1 :(得分:0)

通过" legal" - 如果你的意思是你可以做到,答案是肯定的。它是否正常工作取决于你的工作。

如果您确定自己在自己的记忆空间范围内操作,可以将void *投射到uint16 *或其他任何内容。

这种操作经常在用于视频,压缩等的高速代码中完成。

如果不需要零复制速度,更安全的方法是简单地在堆栈上分配该类型,然后使用memcpy或赋值将其复制以修复对齐。

请参阅Linux内核中的这些对齐宏模式,基本上这样做(如果值已经对齐,编译器可能会对此进行优化):align macro kernel