2

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

#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总是足够大。

4

2 回答 2

1

该标准有意避免要求所有实现都适用于低级编程,但允许旨在用于低级编程的实现通过在比标准规定的更多情况下指定它们的行为来扩展语言以支持这种用法。然而,即使使用为低级编程设计的编译器,使用字符数组作为内存池通常也不是一个好主意。然而,为了与最广泛的编译器和平台兼容,应该将内存池对象声明为具有最广泛对齐的类型的数组,或者包含具有最广泛对齐的类型的 long 字符数组的联合,例如

 static uint64_t my_memory_pool_allocation[(MY_MEMORY_POOL_SIZE+7)/8];
 void *my_memory_pool_start = my_memory_pool_allocation;

或者

 union
 {
   unsigned char bytes[MY_MEMORY_POOL_SIZE];
   double alignment_force;
 } my_memory_pool_allocation;
 void *my_memory_pool_start = my_memory_pool_allocation.bytes;

请注意,clang 和 gcc 可以配置为通过使用标志以适合低级编程的方式扩展语言-fno-strict-aliasing,并且即使使用基于类型的别名,商业编译器通常也可以支持内存池等低级概念,因为它们识别指针类型转换作为可能错误的基于类型的别名假设的障碍。

如果 avoid*被初始化为一个静态对象的地址,该对象的符号在其他上下文中没有使用,我认为任何普通的编译器都不会关心用于初始化的类型。在这里跳过箍遵循标准是愚蠢的差事。不使用时-fno-strict-aliasing,clang 和 gcc 都不会处理标准和 with 规定的所有极端情况-fno-strict-aliasing,并且它们将扩展语言的语义以允许内存池方便地使用,无论标准是否需要它们。

于 2021-05-19T15:23:14.803 回答
1

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

No. The rule in C 2018 6.5 7 says an object defined as array of char may be accessed as:

  1. a type compatible with array of char,
  2. a qualified version of a type compatible with array of char,
  3. a type that is the signed or unsigned type corresponding to array of char,
  4. a type that is the signed or unsigned type corresponding to array of char,
  5. an aggregate or union type that includes array of char among its members, or
  6. a character type.

3 and 4 are not possible for array of char; they apply only when the original type is an integer type. In your various examples with structures, the structures are not types compatible with array of char (nor are their members), ruling out 1 and 2. They do not include array of char among their members, ruling out 5. They are not character types, ruling out 6.

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

The sample output shows that the code produced desired output in one test. This is not equivalent to showing it works as intended.

Is that code safe?

No. The code can be made safe in certain situations. First, declare it with appropriate alignment, such as static _Alignas(max_align_t) memory_pool[256 * 1024];. (max_align_t is defined in <stddef.h>.) That makes the pointer conversions partially defined.

Second, if you are using GCC or Clang and request -fno-strict-aliasing, the compiler provides an extension to the C language that relaxes C 2018 6.5 7. Alternatively, in some cases, it may be possible to deduce from knowledge of the compiler and linker design that your program will work even if 6.5 7 is violated: If the program is compiled in separate translation units, and the object modules contain no type information or no fancy link-time optimization is used, and no aliasing violation occurs in the translation unit that implements the memory pool, then there cannot be adverse consequences from violating 6.5 7 because no way exists for the C implementation to distinguish code that violates 6.5 7 in regard to the memory pool from code that does not. Additionally, you must know that the pointer conversions work as desired, that they effectively produce pointers to the same addresses (rather than merely intermediate data that can be converted back to the original pointer value but not directly used as a pointer to the same memory).

The deduction that there are no adverse consequences is fragile and should be used with care. For example, it is easy to accidentally violate 6.5 7 in the translation unit implementing the memory pool, as by storing a pointer in a freed memory block or by storing size information in a hidden header preceding an allocated block.

于 2021-05-15T10:50:25.440 回答