RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1404382
Accepted
Qwertiy
Qwertiy
Asked:2022-06-27 22:40:16 +0000 UTC2022-06-27 22:40:16 +0000 UTC 2022-06-27 22:40:16 +0000 UTC

如何使用带有可变大小结构的 new?

  • 772

在 C 中,有时会使用这种方法:tio.run

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

struct smth {
  int size;
  int data[0];
};

int main()
{
  smth *x = (smth*)malloc(sizeof (smth) + 3 * sizeof (int));
  x->size = 3;
  x->data[0] = 1;
  x->data[1] = 2;
  x->data[2] = 3;

  for (int q=0; q<x->size; ++q)
    printf("%d ", x->data[q]);

  free(x);
}

它在 C++ 中有类似物吗?如何使用 new 分配所需大小的内存?

c++ c
  • 4 4 个回答
  • 205 Views

4 个回答

  • Voted
  1. Best Answer
    HolyBlackCat
    2022-06-29T03:08:01Z2022-06-29T03:08:01Z

    让我们从您的代码是无效的 C/C++ 的事实开始。可以通过将其替换[0]为[]并将其更改smth为struct smth.

    结构末尾的这种未调整大小的数组称为灵活数组成员,它们在 C++ 中不存在。


    但是您可以自己构建替代方案。

    为此,请从类中删除数组:struct smth {int size;};,然后计算相对于 的“数组”的地址this,例如reinterpret_cast<char *>(this) + sizeof(*this)。然后对齐指针(在你的情况下,没有必要),为了可靠性,你可以通过std::launder.

    我不完全确定这种地址操作是否合法(即使使用std::launder),但我不相信这种流行的技巧会在实践中打破,所以没关系。

    问题:我希望能够写delete x;,而不是用手来写x->~MyClass(); operator delete(x);(operator delete()加号替换在哪里free(&x))。

    亲爱的@ueber 找到了一个链接P0722R1(最终出现在 C++20 中,请参阅标准中的具体更改P0722R3),这说明您不能只delete x;为此类类编写 - 因为现在operator delete()一块内存的大小是自动传入第二个参数,即 . sizeof(T). (他们写道,这使得制造更高效的分配器成为可能。)在您的情况下sizeof(T),内存块大小不匹配,您会得到 UB。

    P0722R1解释解决方案:在你需要添加的类里面

    void operator delete(void *ptr) {::operator delete(ptr);}
    

    这会阻止类大小传递给operator delete,并删除 UB。但P0722R1也解释了“未调整大小”的版本operator delete可能比调整大小的版本慢。

    通过从您的类operator delete的字段计算它可以将内存块的大小传递给,如下所示:但它不能正确完成,因为你的类析构函数已经完成,并且尝试读取它会导致未定义的行为,即使你没有在析构函数中更改它的值。sizevoid operator delete(void *ptr) {::operator delete(ptr, размер);}size

    因此,在 C++20 (this P0722R3) 中加入了所谓的。破坏operator delete(删除的“破坏性”版本)。

    它是这样使用的:

    struct A
    {
        int size = 0;
    
        void operator delete(A *a, std::destroying_delete_t)
        {
            std::size_t mem_size = sizeof(A) + a->size * sizeof(int);
            a->~A();
            ::operator delete((void*)a, mem_size);
        };
    };
    

    通过添加一个参数std::destroying_delete_t,您拒绝自动调用析构函数,并且您必须自己做(因此是“破坏性的”)。这允许您在调用析构函数之前读取类的字段,并计算从中释放的内存的大小。第一个参数的类型也从 更改void *为MyClass *。(有趣的是,在delete第二个参数中带有大小的 Clang 中,它仅使用附加标志 - 进行编译-fsized-deallocation。)


    剩下的只是编写一个漂亮的函数来分配所需的内存量并在其中创建一个对象。类似于以下内容:

    void *ptr = ::operator new(sizeof(A) + size * sizeof(int));
    return new(ptr) A(size);
    

    同一个P0722R1有一个有趣的想法:在operator new类中的自定义计算中执行此计算:

    struct A
    {
        int size = 0;
    
        A(int size) : size(size) {}
    
        void *operator new(std::size_t n, int x)
        {
            return ::operator new(n + x * sizeof(int));
        }
    
        // ... operator delete ...
    };
    

    这个bandura是这样调用的:(new(n) A(n);第一个n传递给new构造函数,第二个传递给构造函数)。这种方法有什么好处尚不清楚。同样,您需要将其包装在一个函数中,以免两次写入大小,并立即返回一个智能指针。


    如果您想让这样的类成为任意类型的模板,您还需要以__STDCPP_DEFAULT_NEW_ALIGNMENT__特殊方式处理对齐程度更高(大于 )的类型。有特殊的重载operator new/operator delete接受对齐 - 你需要单独搜索它们。

    • 3
  2. AR Hovsepyan
    2022-06-28T02:25:49Z2022-06-28T02:25:49Z

    如果我理解正确,那么我们正在讨论如何使用 new 运算符将结构的实例放置在变量内存中。换句话说,这是 new с размещением. 我将尽可能原始地粗略地做问题中显示的内容,以便更清楚:

     struct smth {
        char i{ 'a' };   
    };
    
    int main()
    {    
        char* s1 = new char[5]; 
        // разместить  smth в s1
        smth* ps = new(s1)(smth);
        //инициализация массива через smth
        for(size_t i = 0; i < 4; ++i)
             (ps + i)->i = 'a' + i; //abcde
        //ставим символ конца, чтобы использовать
        s1[4] = '\0';
        //и выводим результат
        std::cout << '\n' << s1; //abcd
        delete[]s1;
        //теперь берем другой участок памяти
        s1 = new char[16];
        //разместить  smth в s1
        smth* ps1 = new(s1)(smth);
        for (size_t i = 0; i < 15; ++i)
            (ps1 + i)->i = 'z' - i; //abcde
        s1[14] = '\0';
        std::cout << '\n' << s1;
        //zyxwvutsrqponm
        
        delete[]s1;
        return 0;
    }
    
    • 2
  3. ueber
    2022-06-27T22:50:27Z2022-06-27T22:50:27Z

    标准 20 中的一项更改专门针对这种用例 - https://wg21.link/p0722。它给出了一个可变大小类的示例:

    class inlined_fixed_string {
      public:
       inlined_fixed_string() = delete;
       const size_t size() const { return size_; }
    
       const char *data() const {
         return static_cast<const char *>(this + 1);
       }
    
       // operator[], etc, with obvious implementations
    
       inlined_fixed_string *Make(const std::string &data) {
         size_t full_size = sizeof(inlined_fixed_string) + data.size();
         return new(::operator new(full_size))
                      inlined_fixed_string(data.size(), data.c_str());
       }
    
      private:
       inlined_fixed_string(size_t size, const char *data) : size_(size) {
         memcpy(data(), data, size);
       }
       size_t size_;
    };
    
    • 1
  4. AlexGlebe
    2022-06-28T16:23:56Z2022-06-28T16:23:56Z

    没有加号之类的东西。您必须使用手动内存分配以及对构造函数和析构函数的调用。我们将示例重新制作成类来演示工作。

    # include <iostream>
    
    class A {
    public:
      int i{};
      A(){std::cout<<"A() "<<std::flush;}
      ~A(){std::cout<<"~A() "<<std::flush;}
    };
    
    class C {
    public  :
      size_t  size  { 0 } ;
      inline  A * Data  ( ) { return  ( A * ) ( this + 1 ) ; }
      C ( size_t ) ;
      ~ C ( ) ;
    } ;
    
    C :: C ( size_t const s ) : size { s } {
      std::cout<<"C() "<<std::flush;
      new ( Data  ( ) ) A [ s ] ;
    }
    
    C :: ~C ( ) {
      A * a = Data  ( ) ;
      for ( size_t  i = 0 ; i < size ; ++ i , ++ a )
        a -> ~ A ( ) ;
      std::cout<<"~C() "<<std::endl;
    }
    
    int main  ( ) {
      int const s = 3 ;
      C * const cp  = ( C * ) :: operator new
        ( sizeof ( C ) + sizeof ( A [ s ] ) ) ;
      new ( cp  ) C ( s ) ;
      cp -> ~ C ( ) ;
      :: operator delete ( cp ) ;
    }
    

    检查

    $ ./vararray 
    C() A() A() A() ~A() ~A() ~A() ~C() 
    

    也就是说,我们为 C 类加上一个数组分配了裸内存。

    构造函数是手动调用的。

    在 C 类的构造函数中,我们手动调用了附加数组中的构造函数。

    类析构函数是手动调用的。

    当调用 C 析构函数时,它会手动调用向量的析构函数,然后清除自身。

    我们放弃分配的裸内存。

    • 1

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5