【问题标题】:How do I allocate memory-aligned C++ object arrays? [duplicate]如何分配内存对齐的 C++ 对象数组? [复制]
【发布时间】:2018-01-04 06:11:27
【问题描述】:

我发现 operator new[] 有问题:

#include <stdlib.h>
#include <stdio.h>

class V4 { public:
    float v[ 4 ];
    V4() {}
    void *operator new( size_t sz ) { return aligned_alloc( 16, sz ); } 
    void *operator new[]( size_t sz ) { printf( "sz: %zu\n", sz ); return aligned_alloc( 16, sz ); }
    void operator delete( void *p, size_t sz ) { free( p ); }
  //void operator delete[]( void *p, size_t sz ) { free( p ); }
};

class W4 { public:
    float w[ 4 ];
    W4() {}
    void *operator new( size_t sz ) { return aligned_alloc( 16, sz ); } 
    void *operator new[]( size_t sz ) { printf( "sz: %zu\n", sz ); return aligned_alloc( 16, sz ); }
    void operator delete( void *p, size_t sz ) { free( p ); }
    void operator delete[]( void *p, size_t sz ) { free( p ); }
};

int main( int argc, char **argv ) { 

    printf( "sizeof( V4 ): %zu\n", sizeof( V4 ));
    V4 *p = new V4[ 1 ];
    printf( "p: %p\n", p );

    printf( "sizeof( W4 ): %zu\n", sizeof( W4 ));
    W4 *q = new W4[ 1 ];
    printf( "q: %p\n", q );

    exit(0);
}

生产:

$ g++ -Wall main.cpp && ./a.out
sizeof( V4 ): 16
sz: 16
p: 0x55be98a10030
sizeof( W4 ): 16
sz: 24
q: 0x55be98a10058

当我包含运算符 delete[] 时,为什么分配大小会增加到 24?这搞砸了我对齐的 malloc。

$ g++ --version
g++ (Debian 7.2.0-18) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

从查看其他问题来看,似乎额外的 8 个字节可能被用于存储数组大小。即使这是预期的行为,为什么它是由 operator delete[] 触发的,分配内存对齐数组的正确过程是什么?

编辑谢谢,链接的问题似乎是相关的。但是,我仍然认为所提出的问题需要答案。在我看来,应该可以更改示例代码以生成内存对齐的数组,而无需求助于 std::vector。我目前的想法是,有必要分配一个更大的 16 字节对齐的字节块,并返回指针,使最初的 8 个字节使块的其余部分在 16 字节边界上对齐。然后 delete[] 运算符必须在调用 free() 之前执行相反的操作。这很恶心,但我认为它需要同时满足调用代码(C 运行时?)(需要 8 个字节来存储大小) - 以及获得 16 字节对齐 Vector4s 的用例。

编辑链接的答案肯定是相关的,但它没有解决确保正确内存对齐的问题。

编辑看起来这段代码会做我想做的事,但我不喜欢 delete[] 中的幻数 8:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

class W16 { public:

    float w[ 16 ];
    W16() {}
    void *operator new( size_t sz ) { return aligned_alloc( 16, sz ); } 
    void *operator new[]( size_t sz ) {
        size_t r = sz % sizeof( W16 );
        size_t ofs = sizeof( W16 ) - r;
        size_t _sz = sz + ofs;
        void *p1 = aligned_alloc( sizeof( W16 ), _sz );
        void *p2 = ((uint8_t *) p1) + ofs;
        printf( "sizeof( W16 ): %zx, sz: %zx, r: %zx, ofs: %zx, _sz: %zx\np1: %p\np2: %p\n\n", sizeof( W16 ), sz, r, ofs, _sz, p1, p2 );
        return p2;
    }
    void operator delete( void *p, size_t sz ) { free( p ); }
    void operator delete[]( void *p, size_t sz ) {
    void *p1 = ((int8_t*) p) + 8 - sizeof( W16 );
        printf( "\np2: %p\np1: %p", p, p1 );
        free( p1 );
    }
};

int main( int argc, char **argv ) {

    printf( "sizeof( W16 ): %zx\n", sizeof( W16 ));
    W16 *q = new W16[ 16 ];
    printf( "&q[0]: %p\n", &q[0] );
    delete[] q;
}

输出:

$ g++ -Wall main.cpp && ./a.out 
sizeof( W16 ): 40
sizeof( W16 ): 40, sz: 408, r: 8, ofs: 38, _sz: 440
p1: 0x559876c68080
p2: 0x559876c680b8

&q[0]: 0x559876c680c0

p2: 0x559876c680b8
p1: 0x559876c68080

EDIT 标题已根据 cmets 中的反馈更改。我认为这不再是链接答案的“重复”,尽管我不知道是否可以将其删除。

【问题讨论】:

  • 与您的问题无关,但 sizeof 运算符的结果是 size_t 类型,不能与 "%d" 格式说明符一起使用。使用例如"%zu" 代替。
  • “也许 g++ 可以对此发出编译器警告” wandbox.org/permlink/TkhBFwqvhSCLgY60 你必须实际启用警告。
  • 也许 g++ 可以对此发出编译器警告 不要使用 printf,使用 cout。
  • 如果你定义new[]而不是delete[],编译器可能假设你永远不会调用标准delete[],因为这样做是UB。所以它不会添加簿记所需的 8 个字节。无论如何,您可能不想使用new[]。将std::vector 与您自己的分配器一起使用。如果您坚持使用new[],请将您分配的内存量四舍五入,以便它可以被对齐方式整除,并且不要使用初始部分。如果您被要求分配例如x=16n+8字节,分配p=aligned_alloc(x+8)并返回p+8;在operator delete[] 打电话给free(p-8)

标签: c++


【解决方案1】:

看起来这对我有用:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

inline void *array_alloc( size_t sz_obj, size_t sz_req ) {
    size_t r = sz_req % sz_obj;
    size_t ofs = sz_obj - r;
    size_t sz = sz_req + ofs;
    void *p1 = aligned_alloc( sz_obj, sz );
    void *p2 = (void*) (((uintptr_t ) p1) + ofs);
  //printf( "sz_obj: %zx, sz_req: %zx, r: %zx, ofs: %zx, sz: %zx\np1: %p\np2: %p\n\n", sz_obj, sz_req, r, ofs, sz, p1, p2 );
    return p2;
}

inline void array_free( size_t sz_obj, void *p2 ) {
    void *p1 = (void*) (((uint8_t*)p2) - (((uintptr_t)p2) % sz_obj));
  //printf( "\np2: %p\np1: %p", p2, p1 );
    free( p1 );
}

class W16 { public:

    float w[ 16 ];
    W16() {}
    void *operator new( size_t sz ) { return aligned_alloc( 16, sz ); } 
    void *operator new[]( size_t sz ) { return array_alloc( sizeof( W16 ), sz ); }
    void operator delete( void *p, size_t sz ) { free( p ); }
    void operator delete[]( void *p, size_t sz ) { array_free( sizeof( W16 ), p ); }
};

int main( int argc, char **argv ) {
  //printf( "sizeof( W16 ): %zx\n", sizeof( W16 ));
    W16 *q = new W16[ 16 ];
    printf( "&q[0]: %p\n", &q[0] );
    delete[] q;
}

编辑感谢 n.m.,此代码无需幻数即可工作。

【讨论】:

  • 如果aligned_alloc 返回的地址是sz_obj 的倍数,那么您传递给free 的地址也必须是sz_obj 的倍数。所以你的幻数是((uintptr_t)p2) % sz_obj
  • 我想你的意思是'p2 - (((uint8_t)p2)%sz_obj)',相当于array_free()中的表达式。可以在代码中开启printfs进行验证。
  • 不,不是(uint8_t)p2((uintptr_t)p2) % sz_obj 是神奇的数字。 (uint8_t)*p2 - ((uintptr_t)p2) % sz_obj 是您传递给 free 的指针。
猜你喜欢
  • 2021-11-30
  • 2021-07-24
  • 2013-12-04
  • 1970-01-01
  • 2021-12-29
  • 2014-03-20
  • 1970-01-01
  • 2016-06-20
相关资源
最近更新 更多