【发布时间】:2009-02-25 00:53:44
【问题描述】:
这在实际问题之前有一些冗长的背景,但是,它有一些解释,希望能消除一些红鲱鱼。
我们的应用程序是用 Microsoft Visual C++ (2005) 开发的,它使用第 3 方库(我们幸运地拥有它的源代码)来导出另一个第 3 方应用程序中使用的压缩文件。该库负责创建导出的文件、管理数据和压缩,并通常处理所有错误。最近,我们开始收到反馈,在某些机器上,我们的应用程序在写入文件时会崩溃。根据一些初步探索,我们能够确定以下几点:
- 崩溃发生在各种硬件设置和操作系统上(尽管我们的客户仅限于 XP / 2000)
- 崩溃总是发生在同一组数据上;但是它们不会出现在所有数据集上
- 对于导致崩溃的一组数据,崩溃无法在所有机器上重现,即使具有相似的特征,即操作系统、RAM 量等。
- 只有在应用程序在安装目录中运行时才会出现该错误 - 不会在从 Visual Studio 构建、在调试模式下运行或什至在用户有权访问的其他目录中运行时
- 无论文件是在本地驱动器还是映射驱动器上构建,都会出现问题
在调查问题后,我们发现问题出在以下代码块中(稍作修改以删除一些宏):
while (size>0) {
do {
nbytes = _write(file->fd, buf, size);
} while (-1==nbytes && EINTR==errno);
if (-1==nbytes) /* error */
throw("file write failed")
assert(nbytes>0);
assert((size_t)nbytes<=size);
size -= (size_t)nbytes;
addr += (haddr_t)nbytes;
buf = (const char*)buf + nbytes;
}
具体来说,_write 返回错误代码 22 或 EINVAL。根据MSDN, _write 返回 EINVAL 意味着缓冲区(在本例中为 buf)是一个空指针。然而,围绕这个函数的一些简单检查验证了对它的任何调用都不是这种情况。
但是,我们确实会使用一些非常大的数据集调用此方法 - 一次调用超过 250MB,具体取决于输入数据。当我们对进入此方法的数据量施加人为限制时,我们似乎已经解决了这个问题。但是,这有点像代码修复了与机器相关/权限相关/取决于月相的问题。那么现在的问题是:
- 有人知道 _write 在一次调用中可以处理的数据量有限制吗?或者 - 除了 _write - Visual C++ 支持的任何文件 I/O 命令?
- 由于并非在所有计算机上都会发生这种情况 - 甚至在每个足够大小的调用上(一个 250 MB 的调用都可以工作,另一个调用不会) - 是否有人知道用户、计算机、组策略设置或会影响此的文件夹权限?
更新: 到目前为止的帖子中的其他几点:
- 我们会处理大型缓冲区分配失败的情况。出于读取我们正在创建的文件的第 3 方应用程序的性能原因,我们希望将所有数据写入一个大块中(尽管鉴于此错误,这可能是不可能的)
- 我们在上面的例程中检查了大小的初始值,它与分配的缓冲区大小相同。此外,当引发 EINVAL 错误代码时,大小等于 0,并且 buf 不是空指针 - 这让我认为这不是问题的原因。
另一个更新:
下面是一个失败示例,上面的代码示例中有一些方便的 printfs。
while (size>0) {
if (NULL == buf)
{
printf("Buffer is null\n");
}
do {
nbytes = _write(file->fd, buf, size);
} while (-1==nbytes && EINTR==errno);
if (-1==nbytes) /* error */
{
if (NULL == buf)
{
printf("Buffer is null post write\n");
}
printf("Error number: %d\n", errno);
printf("Buffer address: %d\n", &buf);
printf("Size: %d\n", size);
throw("file write failed")
}
assert(nbytes>0);
assert((size_t)nbytes<=size);
size -= (size_t)nbytes;
addr += (haddr_t)nbytes;
buf = (const char*)buf + nbytes;
}
如果失败,会打印出来:
Error number: 22
Buffer address: 1194824
Size: 89702400
请注意,没有成功写入任何字节并且缓冲区具有有效地址(并且没有触发 NULL 指针检查,在 _write 之前或之后)
最后更新
不幸的是,我们被事件所克服,无法最终解决这个问题。我们能够找到一些有趣(甚至可能令人不安)的事实。 1. 错误只发生在硬盘写入时间较慢的机器上。具有完全相同硬件规格但 RAID 配置不同(RAID 0 与 RAID 1)的两台 PC 会产生不同的结果。 RAID 0 会正确处理数据; RAID 1 会失败。同样,硬盘速度较慢的旧 PC 也会出现故障;具有更快硬盘驱动器但类似处理器/内存的较新PC可以工作。 2. 写入大小很重要。当我们将传递给 _write 的数据量限制为 64 MB 时,除了一个文件之外的所有文件都成功了。当我们将其限制为 32 MB 时,所有文件都成功了。我们在使用的库中遇到了性能问题 - 这是该库的限制,与 _write 或我们看到的问题无关 - 但这是我们唯一的“软件”修复。
不幸的是,我从来没有得到一个很好的答案(我们正要就此致电 Microsoft,但我们不得不让业务部门签署以支付技术支持电话的费用)关于 EINVAL 为何在第一名。它没有 - 根据我们能够找到的 - 记录在 C 库 API 的任何地方。
如果有人确实找到了一个好的答案,请在此处发布,我会将其标记为答案。我很想为这个传奇得出一个结论,即使它不再直接适用于我。
【问题讨论】:
标签: c++ c debugging visual-c++