c - 是否可以在不违反严格别名的情况下使用字符数组作为内存池?

标签 c language-lawyer strict-aliasing memory-pool

我有一个静态分配的字符数组。我可以在不违反严格别名规则的情况下重用此数组来存储不同类型吗?我不太了解严格别名,但这里有一个代码示例,它可以完成我想做的事情:

#include <stdio.h>

static char memory_pool[256 * 1024];

struct m1
{
    int f1;
    int f2;
};

struct m2
{
    long f1;
    long f2;
};

struct m3
{
    float f1;
    float f2;
    float f3;
};

int main()
{
    void *at;
    struct m1 *m1;
    struct m2 *m2;
    struct m3 *m3;

    at = &memory_pool[0];
    
    m1 = (struct m1 *)at;
    m1->f1 = 10;
    m1->f2 = 20;

    printf("m1->f1 = %d, m1->f2 = %d;\n", m1->f1, m1->f2);

    m2 = (struct m2 *)at;
    m2->f1 = 30L;
    m2->f2 = 40L;

    printf("m2->f1 = %ld, m2->f2 = %ld;\n", m2->f1, m2->f2);

    m3 = (struct m3 *)at;
    m3->f1 = 5.0;
    m3->f2 = 6.0;
    m3->f3 = 7.0;

    printf("m3->f1 = %f, m3->f2 = %f, m3->f3 = %f;\n", m3->f1, m3->f2, m3->f3);

    return 0;
}

我使用 gcc 和 -Wstrict-aliasing=3 -fstrict-aliasing 编译了这段代码,它按预期工作:

m1->f1 = 10, m1->f2 = 20;
m2->f1 = 30, m2->f2 = 40;
m3->f1 = 5.000000, m3->f2 = 6.000000, m3->f3 = 7.000000;

该代码安全吗?假设 memory_pool 始终足够大。

最佳答案

Is it possible to use a character array as a memory pool without violating strict aliasing?

没有。 C 2018 6.5 7 中的规则表示定义为 char 数组的对象可以通过以下方式访问:

  1. char 数组兼容的类型,
  2. char 数组兼容的类型的限定版本,
  3. char 数组对应的有符号或无符号类型的类型,
  4. char 数组对应的有符号或无符号类型的类型,
  5. 包含 char 数组的聚合或 union 类型在其成员中,或
  6. 字符类型。

对于 char 数组来说,3 和 4 是不可能的;它们仅在原始类型是整数类型时适用。在各种结构示例中,结构的类型与 char 数组不兼容。 (它们的成员也不是),排除 1 和 2。它们不包括 char 的数组在其成员中,排除 5。他们不是角色类型,排除 6。

I've compiled this code using gcc with -Wstrict-aliasing=3 -fstrict-aliasing, and it works as intended:

示例输出显示代码在一次测试中产生了所需的输出。这并不等于表明它按预期工作。

Is that code safe?

没有。在某些情况下可以使代码安全。首先,以适当的对齐方式声明它,例如 static _Alignas(max_align_t) memory_pool[256 * 1024]; 。 ( max_align_t<stddef.h> 中定义。)这使得指针转换部分定义。

其次,如果您使用 GCC 或 Clang 并请求 -fno-strict-aliasing ,编译器提供了对 C 语言的扩展,可以放宽 C 2018 6.5 7。或者,在某些情况下,可以从编译器和链接器设计的知识中推断出,即使违反 6.5 7,您的程序也能工作:如果程序是在单独的翻译单元中编译的,并且目标模块不包含类型信息,或者没有使用花哨的链接时优化,并且实现内存池的翻译单元中没有发生别名冲突,那么不会因违反而产生不良后果6.5 7 因为 C 实现无法区分违反内存池 6.5 7 的代码和不违反 6.5 7 的代码。此外,您必须知道指针转换按预期工作,它们有效地生成指向相同地址的指针(而不仅仅是可以转换回原始指针值但不能直接用作指向同一内存的指针的中间数据) .

没有不良后果的推论很脆弱,应谨慎使用。例如,在实现内存池的翻译单元中很容易意外地违反 6.5 7,例如将指针存储在已释放的内存块中或将大小信息存储在已分配 block 之前的隐藏 header 中。

关于c - 是否可以在不违反严格别名的情况下使用字符数组作为内存池?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67543596/

相关文章:

c - C 中文件 I/O 的一些最佳实践是什么?

c++ - 用于任意编程语言或 IR 的 AST

c - 抛弃 const 并读取值是 UB 吗?

c - 为什么这个段错误在 C 中?

c - 在 C 中,LARGE_INTEGER.QuadPart 在 printf 中捕获两次

c++ - 超出命名空间函数定义的参数类型查找

c++ - C 中的空结构与 C++ 中的空结构

c - 一般通过指向字符数组的指针访问数组项?

c++ - 允许将 (double *) 转换为 (double **) 吗?

c++ - 如何在 C++ 中获得没有 UB 的浮点位表示?