为什么printf()允许这个double被指针传递?

时间:2015-09-07 20:26:18

标签: c pointers visual-studio-2008

一对printf()调试语句显示,当我在接收端取消引用时,指向我传递的double的指针将作为不同的值出现 - 但仅限于Microsoft Visual Studio(版本9.0) 。步骤很简单:

    double rho=0;       /* distance from the Earth */
    /* ... */
    for (pass = 0; pass < 2; pass++) {
        /* ... */
        rho = sqrt(rsn*rsn+rp*rp-2*rsn*rp*cpsi*cos(ll));
        printf("\nrho from sqrt(): %f\n", rho);
        /* ... */
    }
    /* ... */
    cir_sky (np, lpd, psi, rp, &rho, lam, bet, lsn, rsn, op);
    /* ... */
}
/* ... */
static void
cir_sky (
/* ... */
double *rho,        /* dist from earth: in as geo, back as geo or topo */
/* ... */)
{
    /* ... */
    printf("\nDEBUG1: *rho=%f\n", *rho);

整个C文件在这里:

https://github.com/brandon-rhodes/pyephem/blob/9cd81a8a7624b447429b6fd8fe9ee0d324991c3f/libastro-3.7.7/circum.c#L366

我原本期望第一个printf()中显示的值与第二个显示的值相同,因为将指针传递给double不应该导致不同的值。事实上,在GCC下,它们总是具有相同的价值。在Visual Studio 32位编译下,它们始终是相同的。但是当使用64位体系结构的Visual Studio编译此代码时,两个double值是不同的!

https://ci.appveyor.com/project/brandon-rhodes/pyephem/build/1.0.18/job/4xu7abnl9vx3n770#L573

rho from sqrt(): 0.029624

DEBUG1: *rho=0.000171

这令人不安。我想知道:在计算rho的位置和指针最终传递的位置之间的代码是否以某种方式通过错误的指针算法破坏了值?所以我在printf()调用的正上方添加了一个cir_sky(),以查看该点是否已经更改了值,或者在调用过程中是否更改了该值:

    printf("\nrho about to be sent: %f\n", rho);
    cir_sky (np, lpd, psi, rp, &rho, lam, bet, lsn, rsn, op);

以下是整个文件上下文中的那一行:

https://github.com/brandon-rhodes/pyephem/blob/28ba4bee9ec84f58cfffabeda87cc01e972c86f6/libastro-3.7.7/circum.c#L382

猜猜是什么?

添加printf()修复了错误 - 传递给rho的指针现在可以取消引用到正确的值!

从这里可以看出:

https://ci.appveyor.com/project/brandon-rhodes/pyephem/build/1.0.19/job/s3nh90sk88cpn2ee#L567

rho from sqrt(): 0.029624

rho about to be sent: 0.029624

DEBUG1: *rho=0.029624

我很神秘。

我遇到C标准的边缘情况?为什么仅在此函数的顶级范围中使用值rho会强制Microsoft编译器正确保留其值?是rho是否在一个块内设置和使用的问题,并且Visual Studio不设法将其值保留在该块之外,因为我从未完全内化的C标准的怪癖?

您可以在上面的AppVeyor链接中看到整个构建输出。如果问题可能是调用Visual Studio的方式或编译选项,则此C文件的特定编译步骤为:

C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\Bin\amd64\cl.exe /c /nologo /Ox /MD /W3 /GS- /DNDEBUG -Ilibastro-3.7.7 -IC:\Python27-x64\include -IC:\Python27-x64\PC /Tclibastro-3.7.7\circum.c /Fobuild\temp.win-amd64-2.7\Release\libastro-3.7.7\circum.obj
circum.c
libastro-3.7.7\circum.c(126) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
libastro-3.7.7\circum.c(127) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data
libastro-3.7.7\circum.c(139) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
libastro-3.7.7\circum.c(140) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
libastro-3.7.7\circum.c(295) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
libastro-3.7.7\circum.c(296) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data
libastro-3.7.7\circum.c(729) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
libastro-3.7.7\circum.c(730) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data

从我所看到的那些警告中,对于这个特定难题所涉及的代码,这些警告都没有 - 即使它们是,所有它们都表示浮点值可能变得不那么精确(从小数精度的大约15位数) 7),而不是它可以完全改变。

这里再次是两次编译和测试运行的输出,第一次运行失败,第二次运行 - 由于printf()? - 成功了:

https://ci.appveyor.com/project/brandon-rhodes/pyephem/build/1.0.18/job/4xu7abnl9vx3n770

https://ci.appveyor.com/project/brandon-rhodes/pyephem/build/1.0.19/job/s3nh90sk88cpn2ee

根据AppVeyor的说法,两者都是完全相同的架构:

Environment: PYTHON=C:\Python27-x64, PYTHON_VERSION=2.7.x, PYTHON_ARCH=64, WINDOWS_SDK_VERSION=v7.0

2 个答案:

答案 0 :(得分:1)

我快速查看这段代码并没有提出任何突出的错误或有问题的东西。但是,当printf解决问题时,这意味着存在一些非确定性。让我们分析可能的原因:

  1. 并发 - 数据竞争:最常见,但你说它是单线程的。
  2. 未初始化的内存:rho已在此处初始化,但是,也许其他地方的内容并非如此,而且它会搞砸。我运行valgrind(在Linux上)和AdressSanitizer以及其他清洁剂(也应该在Windows上用于clang和gcc),看看他们是否想出了什么。
  3. 狂野指针和其他越界访问:我们在这里看到的代码中没有任何内容,但是它调用了其他函数。再次,运行valgrind和消毒剂。
  4. 如果之前的步骤很短,那么下一个最可能的候选者就是MSVC错误。 MSVC因在某些复杂代码中弄乱事物而臭名昭着,这有点复杂。很多时候我重新安排代码只是为了让MSVC开心。有时关闭优化有帮助,有时它不会。同样尝试不同的编译器选项。有时会有一个更新/补丁有帮助,有时候没有。同样适用于下一版MSVC。我建议在调试器中查看反汇编程序,但是你说你无法访问Windows机器。这里最好的选择是尝试简化代码 - 使函数更小,减少参数数量。
  5. 还有其他可能的原因。例如,堆栈可能由于某种原因搞砸了 - 可能是在与Python运行时进行交互时。尝试建立&amp;将其作为&#34;常规&#34; C代码,而不是Python扩展。消除对其他功能的调用(如果它与计算混淆,更别提,你只是试图找出问题)。
  6. 无论如何,我建议您亲自操作Windows机器并对其进行调试。根据我的经验,这是解决这些问题的最佳途径。

答案 1 :(得分:0)

这是(错误的)优化的影响吗?

关闭任何优化(DEBUG?),看看你是否有同样的效果。

当然,如果你发现它是优化器,那么你就可以完成任务并且只能愚弄它,例如一个什么都不做的sprintf。

另外,printf也可以打印出指针(“%16x”,(long)&amp; rho)而不是我认为它是不正确的,但是如果我们缺少summat那么只是作为一个健全条款。此外,大多数双倍随机位的结果通常最终在E +/- 317范围内,因此0.000171结果有点太合理而不能完全怀疑。