编码大于 15000 * 15000 像素的 PNG 文件时,LodePNG 崩溃

Dum*_*fus 1 c png

我一直在使用 LodePNGlodepng_encode24_file对一些 24 位 RGB 图像文件进行编码,到目前为止它工作得非常好。但是,我注意到当我向它提供大于 15360*15360 像素大小的数据集时它似乎崩溃了(14336*14336 像素图像编码良好)。

对于 32 位情况(崩溃前的最大大小略低),可以通过简单地替换该行来获得此行为的最小示例

  unsigned width = 512, height = 512;
Run Code Online (Sandbox Code Playgroud)

  unsigned width = 1024*14, height = 1024*14;
Run Code Online (Sandbox Code Playgroud)

LodePNG 的 example_encode.c 文件中,并执行它。

我之前遇到过 C 代码崩溃的问题,因为我将大数组分配到堆栈内存(其最大大小通常在 2MB 左右)而不是堆内存,所以作为 C 的新用户,我的第一直觉是看看是否有堆内存大小的上限。

但是,根据这个答案,堆内存没有限制,所以一定是其他地方出了问题。

我的第二个猜测是崩溃是由于 PNG 格式本身支持的最大图像尺寸的固有限制。但是,根据此答案及其下方的评论,PNG 支持的最大文件大小约为 4,000,000,000 * 4,000,000,000 像素,因此这也不是罪魁祸首。

有没有人猜测可能会出什么问题?其他人在尝试时是否能够重现此错误?

编辑:就 RAM 消耗而言,我有 8GB RAM,减去硬件保留的、使用中的、修改过的和备用内存(Windows 资源监视器实用程序使用的术语),在进行计算时,我有大约 4GB 的可用 RAM。对于 15000*15000 大小的 32 位图像,需要小于 1GB。同样,当我成功编码 14000*14000 24 位图像时,我的可用 RAM 在编码过程的任何时候都不会低于 3GB,所以我认为 RAM 耗尽不是问题。

ues*_*esp 5

我相信您低估了程序使用的内存量。假设您使用的是 Windows,那么您可能只有 2 GB 的内存可供进程使用(请参阅此处)。然后为 14336x14336 图像分配一个 882 MB 的大块。然后 LodePNG 代码执行更多的分配,很可能等于或大于此图像大小。

我只手动跟踪了LodePNG 代码,但它似乎在内存中创建了一个缓冲区并以块的形式写入该缓冲区。它在重新分配时进行最小调整(in lodepng_chunk_append(),仅足以容纳数据加上 12 个字节),这意味着它将不得不进行大量重新分配。这可能(或最终会)将内存碎片化到一个非常大的缓冲区不可用的程度。

即使堆内存没有碎片化,想想当您尝试将realloc()800MB 缓冲区变为 801MB 缓冲区时可能会发生什么。如果堆管理器是“智能”的,它可能会工作,但一个简单的将需要总共 1601MB ......如果您的堆大小为 2000MB 并且您已经使用了其中的 822MB,那么它不可用。

很多(大部分)都是猜测,但您可以自己做一些测试来模拟分配和重新分配几个大内存块,看看是否可以重现内存不足的情况。

附录:虽然上述可能是正确的,但实际上并不是这种情况下崩溃的原因。从实际运行和跟踪代码来看,问题是LodePng.c 中的第 5558

size_t size = (w * h * lodepng_get_bpp(&info.color) + 7) / 8;
Run Code Online (Sandbox Code Playgroud)

w * hlodepng_get_bpp()返回 24时,当大于 178,956,970 时,此计算会导致整数溢出。对于方形图像,这是 13378 x 13378。这会导致分配的输出缓冲区大小不正确,从而导致稍后写入时缓冲区溢出。

快速解决方法是将此行更改为:

 size_t size = (size_t) ((unsigned long long) w * h * lodepng_get_bpp(&info.color) / 8 + 1);
Run Code Online (Sandbox Code Playgroud)

尽管我不确定这是否适用于所有 BPP 值,但它仍然存在溢出问题,尽管尺寸更大。我建议联系 LodePNG 的作者以实施适当的修复。