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

Bra*_*des 10 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);
Run Code Online (Sandbox Code Playgroud)

整个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
Run Code Online (Sandbox Code Playgroud)

这令人不安.我想知道:在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);
Run Code Online (Sandbox Code Playgroud)

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

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

你猜怎么着?

添加printf()修复的bug - 传递给的指针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
Run Code Online (Sandbox Code Playgroud)

我很神秘.

我遇到了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
Run Code Online (Sandbox Code Playgroud)

从我所看到的,这些警告中没有一个是涉及这个特定难题的代码 - 即使它们是,它们所表示的全部是浮点值可能变得不那么精确(从小数精度的大约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
Run Code Online (Sandbox Code Playgroud)

Bri*_*ich 0

这是(错误的)优化的结果吗?

关闭所有优化(DEBUG?)并查看是否获得相同的效果。

当然,如果您发现它是优化器,那么您就麻烦了,只能做一些事情来愚弄它,例如不执行任何操作的 sprintf。

另外,你的 printf 还可以打印出指针(“%16x”,(长)&rho),并不是我认为它是不正确的,而是作为一个理智子句,以防我们丢失 summat。此外,大多数具有随机位的双精度数的结果通常最终在 E+/-317 范围内,因此 0.000171 结果有点过于合理,无法完全怀疑。

  • 是的,我还没有 50r,所以这是我能做的最好的了。 (2认同)
  • 这似乎是尝试追查问题的合理答案。如果问题可以通过调试版本重现,那么如果 VS 的调试器包含观察点类型中断,则可以使用它来检查对 rho 的任何写入,以查看是什么损坏了它。 (2认同)