std::make_unique 数组导致损坏的顶部大小错误

问题描述 投票:0回答:1

根据如何创建我的 unique_ptr,它要么有效,要么导致执行时出现

malloc(): corrupted top size
错误。

这会导致上述错误

#include <tuple>
#include <string>
#include <memory>
#include <fstream>
#include <iostream>
#include <filesystem>

std::tuple<std::unique_ptr<uint8_t*>, size_t> getBinaryData( const std::filesystem::path &sourcePath )
{
    std::ifstream filestream ( sourcePath, std::ios::binary );

    const size_t filesize = std::filesystem::file_size(sourcePath);

    std::cout << "Input file size: " << filesize << " bytes\n";
    
    std::unique_ptr<uint8_t*> buffer = std::make_unique<uint8_t*>( new uint8_t[ filesize ] );
    if ( !filestream.read( ( char* )( buffer.get() ), filesize ) )
    {
        std::cout << << "Reading from file " << sourcePath << " failed." << "\n";
    }
    
    filestream.close();

    return { std::move( buffer ), filesize };
}

int main()
{
    auto [memoryPtr, length] = getBinary( "myfile.bin" );
}

这有效:

std::tuple<std::unique_ptr<uint8_t, std::default_delete<uint8_t[]>>, size_t> getBinaryData( const std::filesystem::path &sourcePath )
{
    std::ifstream filestream ( sourcePath, std::ios::binary );

    const size_t filesize = std::filesystem::file_size(sourcePath);

    std::cout << "Input file size: " << filesize << " bytes\n";

    std::unique_ptr<uint8_t, std::default_delete<uint8_t[]>> buffer( new UInt8_t[ filesize ] );
    if ( !filestream.read( ( char* )( buffer.get() ), filesize ) )
    {
        std::cout << "Reading from file " << sourcePath << " failed." << "\n";
    }
    
    filestream.close();

    return { std::move( buffer ), filesize };
}

但我不喜欢第二种解决方案,因为我必须到处写

std::default_delete<uint8_t[]>

你知道我在第一种方法中做错了什么吗?

了解为什么这不起作用也很有趣:

std::tuple<std::unique_ptr<uint8_t*>, size_t> getBinaryData( const std::filesystem::path &sourcePath )
{
    std::ifstream filestream ( sourcePath, std::ios::binary );

    const size_t filesize = std::filesystem::file_size(sourcePath);

    std::cout << "Input file size: " << filesize << " bytes\n";

    std::unique_ptr<uint8_t*> buffer( new uint8_t[ filesize ] );
    if ( !filestream.read( ( char* )( buffer.get() ), filesize ) )
    {
        std::cout << "Reading from file " << sourcePath << " failed." << "\n";
    }
    
    filestream.close();

    return { std::move( buffer ), filesize };
}
c++ smart-pointers
1个回答
0
投票
  1. C++ 对
    new
    /
    delete
    new[]
    /
    delete[]
    进行了严格区分。您不能使用一个运算符分配内存,然后使用另一个运算符释放内存。
  2. std::unique_ptr<Type>
    在销毁时调用
    delete
    ,并且
    std::make_unique<Type>()
    调用
    new
    std::unique_ptr<Type[]>
    在销毁时调用
    delete[]
    ,并且
    std::make_unique<Type[]>
    接受数组的大小作为其参数并调用
    new[]
  3. std::unique_ptr<Type*>
    在销毁时调用
    delete
    并释放Type*
    类型的指针,而不是它指向的内存
考虑到所有这些,让我们看一下代码:

std::unique_ptr<uint8_t*> buffer = std::make_unique<uint8_t*>( new uint8_t[ filesize ] );
在这里,您分配了一个由 

filesize

uint8_t
 元素组成的数组,然后分配 1 个 
uint8_t*
 类型的对象,并将数组的地址复制到该变量中。该地址
的地址存储在buffer
中。

if ( !filestream.read( ( char* )( buffer.get() ), filesize ) )

buffer.get()

返回
uint8_t**
类型的值,一个指向指针大小的内存块的指针。您以 C 风格将其转换为 
char*
 并尝试向其中写入 
filesize
 字节。如果 
filesize
 大于 
sizeof(uint8_t*)
,则您正在分配的内存之外进行写入。此时任何事情都可能发生,如果你的程序只是崩溃了,那你就很幸运了。此时您很可能会损坏内部堆结构。

return { std::move( buffer ), filesize };
万一 

filesize <= sizeof(uint8_t*)

 你(正确地)返回 
buffer
 变量。最终它将被销毁,并且 
buffer
(指针的大小)拥有的内存将被正确释放。如果堆在上面被损坏,这将是库函数可能“注意到”它的点。不管怎样,您分配的 
filesize
 大小的块将永远不会被释放,内存已经“泄漏”。

在另一次尝试中,你写了

std::unique_ptr<uint8_t*> buffer( new uint8_t[ filesize ] );
在这里,您再次分配 

filesize

uint8_t
 元素,并将该块的地址写入 
buffer
。不会发生内存泄漏。不幸的是,在销毁时 
buffer
 将调用 
delete
 (因为它无法判断您是否存储了数组或只是单个值),并且您将得到 
new[]
/
delete
 不匹配,这将再次导致
最好的情况是 立即使你的程序崩溃,并可能在你的应用程序中引入看似神秘的错误。

你可以做什么:

//no manual "new"! std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(filesize); //the rest is the same sans changing uint8_t* to uint8_t[]
但是,大多数时候您最好使用 

std::vector<uint8_t>

 来代替。数组指针不存储大小(这就是您必须返回元组的原因),并且可以轻松寻址分配内存之外的值。

© www.soinside.com 2019 - 2024. All rights reserved.