【问题标题】:SDL why create textures on the heap instead of stackSDL 为什么在堆而不是堆栈上创建纹理
【发布时间】:2016-10-15 22:38:12
【问题描述】:

所以我一直在阅读lazyfoos SDL2 教程,我只是好奇为什么所有SDL_Surface/SDL_textures 等都是在堆上创建的?我意识到 SDL 本身是用 C 语言编写的,并且函数接受/返回指针,但为什么不在堆栈上创建表面并将其引用传递给函数呢? (或在分配时取消引用它)

希望我已经清楚了。这里有一段代码sn-p来进一步解释一下:

为什么会这样:

void pointer()
{
    SDL_Surface *windowsurface;
    SDL_Surface *surface = SDL_LoadBMP("asdf.bmp");
    SDL_BlitSurface(surface, 0, windowsurface, 0);
}

而不是这个:

void stack()
{
    SDL_Surface windowsurface;
    SDL_Surface surface = *(SDL_LoadBMP("asdf.bmp"));
    SDL_Blit(&surface, 0, &windowsurface, 0);
}

如果后者确实可行,您还需要致电SDL_FreeSurface 吗?如果是这样,这将确保您不会泄漏任何内存,所以我不太了解前者的好处。

希望这一切都有意义,在 SDL 方面我仍然是新手

【问题讨论】:

  • 这很可能是因为 SDL 的对象具有 SDL 库必须跟踪的内部资源。这要求 SDL 对象的所有实例化和销毁都由库直接完成,只有库负责 100% 的对象生命周期管理;因此唯一可能的实现是基于堆的分配和销毁。
  • 所以我必须在堆上创建所有表面?我问的原因是因为我有很多以纹理为成员的类,并且希望避免担心我所有类的复制构造函数和分配。
  • 在第二个示例中,windowsurface 在函数退出时被销毁。我想大多数纹理需要比创建它们的函数寿命更长。
  • 通常的解决方案是为原生 C 对象提供一个包装器,就像 SDL 库中的那些,使用 std::shared_ptr 来引用对象,并让包装器负责销毁本机使用库的适当析构函数的 C 对象。
  • 堆栈大小,一般来说不适合图片。默认linux栈大小为8M(ulimit -s查看)。

标签: c++ pointers memory sdl heap-memory


【解决方案1】:

我也喜欢在不需要时避免使用指针,但许多游戏程序员在不需要时使用指针,用 new 分配几乎任何大的东西,使其成为全局 (!),并在最后调用 delete。

使用动态分配有一个小优势(超过了指针错误的风险,IMJ)。当你在两个对象之间分配时,你需要一个拷贝ctor,这意味着你必须决定是否共享内存。这通常是个坏主意,如果您不这样做,您的复制 ctor 可能需要做很多工作(而复制指针非常便宜)。更高版本的 C++ 消除了部分问题,但不是全部。

但在 SDL 中,SDL 的选择确实迫使您不仅使用 delete 或 free,还使用其特殊功能 SDL_FreeSurfaceSDL_DestroyTexture 等来解除分配。对于使用此类功能的结构,您只需卡住。因此,SDL 程序员动态地分配这些东西,即使他们不是认为眼前的一切都应该成为指针的铁杆游戏程序员。您可以编写包装器来稍微清理一下,但在下面,您仍然被它们的删除例程所困扰。

【讨论】:

    【解决方案2】:

    这种方法存在一些问题:

    void stack()
    {
        SDL_Surface windowsurface;
        SDL_Surface surface = *(SDL_LoadBMP("asdf.bmp"));
        SDL_Blit(&surface, 0, &windowsurface, 0);
    }
    

    首先,对象windowsurfacesurface将在函数退出时被销毁,因为创建它们的堆栈部分将被调用函数回收。

    其次,当它们被销毁时,它将通过调用 delete 的等效项来完成,这可能不是这些对象的正确删除器。该库提供了自己需要调用的删除函数。

    最后,位图对象的surface 的分配可能会很慢,因为位图可能很大。分配指针要快得多。

    管理从空闲存储(堆)分配的对象的一种安全方法是使用智能指针

    // create a special deleter that calls the correct function
    // to delete the object
    struct SDL_Surface_deleter
    {
        void operator()(SDL_Surface* surface) const { SDL_FreeSurface(surface); }
    };
    
    // some type aliases for convenience
    using SDL_Surface_uptr = std::unique_ptr<SDL_Surface, SDL_Surface_deleter>;
    using SDL_Surface_sptr = std::shared_ptr<SDL_Surface>;
    
    SDL_Surface_uptr make_unique_surface(SDL_Surface* surface)
    {
        return SDL_Surface_uptr{surface};
    }
    
    SDL_Surface_sptr make_shared_surface(SDL_Surface* surface)
    {
        return {surface, SDL_Surface_deleter{}};
    }
    
    void smart_objects()
    {
        // no need to delete this
        auto unique_surface = make_unique_surface(SDL_LoadBMP("asdf.bmp"));
    
        // no need to delete this either
        auto shared_surface = make_shared_surface(SDL_LoadBMP("asdf.bmp"));
    
        // stuff...
    }
    

    The Manual 中查找std::unique_ptrstd::shared_ptr

    【讨论】:

    • 感谢您的回复!所以使用智能指针我能够避免创建复制构造函数/分配?我想我需要更多地研究智能指针。
    【解决方案3】:

    这有很多原因,但我只关注一个。具体来说,在 C++ 中,没有可移植的方法来在堆栈上创建一个在编译时大小未知的对象。

    这是一个严重的问题,因为编译器不知道纹理的大小,因为它不知道加载到其中的数据会有多大。在 C 语言中,使用 VLA 可能会有所帮助,但由于许多操作系统上可用的堆栈大小较小,因此对于大型对象仍然不建议使用 VLA。

    除此之外,纹理可以在 GPU 内存而不是主内存中实现。这绝对不能在堆栈上,并且必须由正在使用的任何系统中的图形例程管理。这些图形系统通常只提供一个指向 GPU 内存中纹理的不透明指针,可以通过提供的例程释放或管理纹理。

    现在您可能会争辩说句柄结构至少可以存在于堆栈中,但实际上这提供了最小的节省,因为绝大多数读取和写入将是纹理本身而不是句柄对象,因此优化几乎没有价值。

    【讨论】:

      猜你喜欢
      • 2021-08-09
      • 2013-11-08
      • 1970-01-01
      • 1970-01-01
      • 2016-02-27
      • 2020-10-27
      • 2013-04-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多