如何在一次malloc调用中为数组和结构分配内存而不破坏严格的别名?

如何在一次malloc调用中为数组和结构分配内存而不破坏严格的别名?,c,memory-management,strict-aliasing,C,Memory Management,Strict Aliasing,为可变大小的数组分配内存时,我通常会执行以下操作: struct array { long length; int *mem; }; struct array *alloc_array( long length) { struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length); arr->length = length; arr->mem = (int *)(a

为可变大小的数组分配内存时,我通常会执行以下操作:

struct array {
    long length;
    int *mem;
};

struct array *alloc_array( long length)
{
    struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length);
    arr->length = length;
    arr->mem = (int *)(arr + 1); /* dubious pointer manipulation */
    return arr;
}
int main()
{
    struct array *arr = alloc_array( 10);
    for( int i = 0; i < 10; i++)
        arr->mem[i] = i;
    /* do something more meaningful */
    free( arr);
    return 0;
}
然后我使用arrray,如下所示:

struct array {
    long length;
    int *mem;
};

struct array *alloc_array( long length)
{
    struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length);
    arr->length = length;
    arr->mem = (int *)(arr + 1); /* dubious pointer manipulation */
    return arr;
}
int main()
{
    struct array *arr = alloc_array( 10);
    for( int i = 0; i < 10; i++)
        arr->mem[i] = i;
    /* do something more meaningful */
    free( arr);
    return 0;
}
intmain()
{
结构数组*arr=alloc_数组(10);
对于(int i=0;i<10;i++)
arr->mem[i]=i;
/*做些更有意义的事*/
免费(arr);
返回0;
}
这可以在没有警告的情况下工作和编译。然而,最近我读到了关于严格别名的文章。据我所知,上述代码在严格别名方面是合法的,因为通过
int*
访问的内存不是通过
struct array*
访问的内存。代码实际上是否违反了严格的别名规则?如果是这样,如何修改它以不破坏它们


我知道我可以分别分配结构和数组,但是我也需要分别释放它们,大概是在某种
free\u array
函数中。这意味着我必须知道在释放内存时释放的内存类型,这会使代码复杂化。它也可能会更慢。这不是我想要的。

在结构中声明灵活数组成员的正确方法如下:

struct array {
    long length;
    int mem[];
};
struct array {
    long length;
    int mem[];
};
然后,您可以像以前一样分配空间,而无需将任何内容分配给
mem

struct array *alloc_array( long length)
{
    struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length);
    arr->length = length;
    return arr;
}

现代C语言正式支持。因此,您可以按如下方式定义您的结构:

struct array {
    long length;
    int mem[];
};
struct array {
    long length;
    int mem[];
};
并像现在一样分配它,而不需要增加可疑指针操作的麻烦。它将开箱即用,所有访问都将正确对齐,您不必担心语言的黑暗角落。当然,只有当你有一个这样的成员需要分配时,它才是可行的


至于您现在拥有的,由于分配的存储没有声明的类型(这是一块空白板),您没有打破严格的别名,因为您没有为该内存提供有效的类型。唯一的问题是路线可能会混乱。尽管对于您的结构中的类型,这是不可能的。

我相信编写的代码确实违反了严格的别名规则,当标准以最严格的意义读取时

您正在通过指向不相关类型的数组的指针访问类型为
int
的对象。我相信,一个简单的解决方法是使用结构的起始地址,然后将其转换为char*,并对其执行指针算法。例如:

void* alloc = malloc(...);
array = alloc;
int* p_int = (char*)alloc + sizeof(array);

这仅在结构包含一个可变长度元素时有效。如果还有更多,那么就我所知,像我概述的那样进行指针操作是必要的。严格的别名规则合法吗?@ego你的问题没有具体说明这一点。在这种情况下,您不会有任何严格的别名冲突,因为所讨论的内存是malloc'ed并且还没有类型,但是您可能会遇到对齐问题。在这种情况下,您最好的选择可能是只对结构及其包含的数组进行单独的分配。不管怎样,当你释放它的时候,你需要知道你释放了什么,而且你不会看到任何明显的速度变化。@dbush你是说将程序中的
malloc
free
调用的数量增加一倍不会引起速度的明显变化吗?灵活数组成员是一种方法,只要最后只需要一个这样的成员(并且至少存在一个其他成员)。是
(char*)alloc+sizeof(数组);
当然为
int
对齐?
alloc
可以,但是
sizeof(数组)
没有指定为int的倍数。我希望这会失败,尽管只有敌对或独角兽平台。@chux我几乎忘记了对齐。有一段时间没有处理过关心对齐的平台。我一直在关注别名问题,但当然,对齐可能非常重要。对于al,我也是如此直到一个挑剔的平台和十几条总线出现故障后才点火。@SergeyA是的,未对齐的访问速度较慢。但是在ARM上(现在最流行的通用计算平台?)未对齐的访问确实会引发异常。Sergey和@immibis:当gcc自动矢量化时,破坏x86-64上的C对齐规则可能会导致正确性问题,而不仅仅是性能问题。顺便说一句,在现代x86-64上,如果未对齐的访问跨越缓存线边界(或者AMD CPU上的32字节边界),则其速度只会较慢。此外,如果您跨缓存线边界未对齐
原子\u int
,它将不再是原子的(对于加载或存储,对于原子RMW,它将非常慢。)首选
sizeof expr
而不是
sizeof(TYPE)
。重复该类型容易出错。而不是说“现代”,您应该指定它从C99开始就可用。