什么时候除以零而不是除以零?调试器中的一个难题(静态变量问题)

时间:2015-01-22 19:20:23

标签: c++ debugging windbg

我很困惑,我认为我的调试器对我说谎。我的代码中有以下循环:

MyClass::UploadFile(CString strFile)
{
  ...
  static DWORD dwLockWaitTime = EngKey::GetDWORD(DNENG_SERVER_UPLOAD_LOCK_WAIT_TIME, DNENG_SERVER_UPLOAD_LOCK_WAIT_TIME_DEFAULT);
  static DWORD dwLockPollInterval = EngKey::GetDWORD(DNENG_SERVER_UPLOAD_LOCK_POLL_INTERVAL, DNENG_SERVER_UPLOAD_LOCK_POLL_INTERVAL_DEFAULT);

  LONGLONG llReturnedOffset(0LL);
  BOOL bLocked(FALSE);
  for (DWORD sanity = 0; (sanity == 0 || status == RESUMABLE_FILE_LOCKED) && sanity < (dwLockWaitTime / dwLockPollInterval); sanity++) 
    {
      ...

在我的程序过程中,这个循环已被执行了数百次,并且两个静态变量在代码中的任何地方都没有改变,当它们被静态初始化并在循环条件下读取时,它们只被写入一次在另一个地方。由于它们是从Windows注册表中读取的用户设置,因此它们几乎总是具有dwLockWaitTime = 60和dwLockPollInterval = 5的常量值。因此循环始终为60/5。

很少,我得到一个崩溃转储,显示这行代码已经抛出除零错误。我已经检查了WinDbg所说的内容并显示:

FAULTING_IP: 
procname!CServerAgent::ResumableUpload+54a [serveragent.cpp @ 725]
00000001`3f72d74a f73570151c00    div     eax,dword ptr [proc!dwLockPollInterval (00000001`3f8eecc0)]

EXCEPTION_RECORD:  ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 000000013f72d74a (proc!CServerAgent::ResumableUpload+0x000000000000054a)
   ExceptionCode: c0000094 (Integer divide-by-zero)
  ExceptionFlags: 00000000
NumberParameters: 0

ERROR_CODE: (NTSTATUS) 0xc0000094 - {EXCEPTION}  Integer division by zero.

我检查了汇编程序代码,它显示崩溃发生在这个div指令上。

00000001`3f72d744 8b0572151c00    mov     eax,dword ptr [dwLockWaitTime (00000001`3f8eecbc)]
00000001`3f72d74a f73570151c00    div     eax,dword ptr [dwLockPollInterval (00000001`3f8eecc0)]

因为您可以看到000000013f8eecbc的值已移至eax,然后eax除以000000013f8eecc0的值。

你问的那两个值是什么?

0:048> dd 00000001`3f8eecbc
00000001`3f8eecbc  0000003c 00000005 00000001 00000000
00000001`3f8eeccc  00000000 00000002 00000000 00000000
00000001`3f8eecdc  00000000 7fffffff a9ad25cf 7fffffff
00000001`3f8eecec  a9ad25cf 00000000 00000000 00000000
00000001`3f8eecfc  00000000 00000000 00000000 00000000
00000001`3f8eed0c  00000000 00000000 00000000 00000000
00000001`3f8eed1c  00000000 00000000 00000000 00000000
00000001`3f8eed2c  00000000 00000000 00000000 00000000
0:048> dd 000000013f8eecc0
00000001`3f8eecc0  00000005 00000001 00000000 00000000
00000001`3f8eecd0  00000002 00000000 00000000 00000000
00000001`3f8eece0  7fffffff a9ad25cf 7fffffff a9ad25cf
00000001`3f8eecf0  00000000 00000000 00000000 00000000
00000001`3f8eed00  00000000 00000000 00000000 00000000
00000001`3f8eed10  00000000 00000000 00000000 00000000
00000001`3f8eed20  00000000 00000000 00000000 00000000
00000001`3f8eed30  00000000 00000000 00000000 00000000

常量605完全符合我的预期。那么除以零在哪里???我的调试器在说谎吗?当然,硬件会抛出除以零,所以它不会犯错误吗?如果它在我的代码中的不同位置被零除,那么调试器在这个位置显示指令指针的几率是多少?我承认,我很难过......

2 个答案:

答案 0 :(得分:10)

由于代码是成员函数的一部分,并且您从多个线程调用此函数,因此如果使用不符合C ++ 11标准的编译器,则static变量不是线程安全的。因此,在初始化这两个静态变量时,您可能会获得数据竞争。

对于符合C ++ 11标准的编译器,现在保证静态变量由第一个线程初始化,而后续线程等待静态初始化。

对于Visual Studio 2010及以下,静态局部变量不保证是线程安全的,因为这些编译器符合C ++ 03和C ++ 98标准。

对于Visual Studio 2013,我不确定静态本地初始化方面的C ++ 11支持级别。因此,对于Visual Studio 2013,您可能必须使用正确的同步来确保正确初始化静态局部变量。

对于Visual Studio 2015,此项已得到解决,并且完全实现了正确的静态本地初始化,因此您当前拥有的代码应该适用于VS 2015及更高版本。


编辑:对于Visual Studio 2013,未实现静态本地线程安全初始化(“Magic Statics”),as described here

因此,我们可以谨慎地验证原始问题的原因是静态本地初始化问题和线程。因此,解决方案(如果您想坚持使用VS 2013)是使用正确的同步,或重新设计您的应用程序,以便不再需要静态变量。

答案 1 :(得分:7)

问题可能与多线程有关。

  1. 线程进入函数
  2. 检查隐藏的“is_initialized”静态变量以查看是否已执行初始化
  3. var为0,因此它将变量设置为1并继续读取注册表
  4. 此时另一个线程进入函数
  5. 第二个线程将变量视为已初始化并跳过初始化代码
  6. 当分母仍为0(第一个线程仍在读取注册表)时执行除法
  7. 程序崩溃,但同时第一个线程完成执行,设置你在转储中看到的变量。
  8. 你会想着不可能发生的事情而失眠