簡體   English   中英

為什么printf()允許這個double被指針傳遞?

[英]Why does a printf() allow this double to be passed by pointer?

一對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之間的代碼和指針最終傳遞的位置之間的代碼是否會以錯誤的指針算法破壞值? 所以我在cir_sky()調用的正上方添加了最后一個printf() ,以查看該點是否已經更改了值,或者它是否在調用過程中被更改:

    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

我快速查看這段代碼並沒有提出任何突出的錯誤或有問題的東西。 但是,當printf解決問題時,這意味着存在一些非確定性。 讓我們分析可能的原因:

  1. 並發 - 數據競爭:最常見,但你說它是單線程的。
  2. 未初始化的內存: rho在這里被初始化,但是,也許其他地方的東西不是,而且它正在搞亂。 我會運行valgrind(在Linux上)和AdressSanitizer以及其他清潔劑(對於Windows也應該在clang和gcc上使用),看看他們是否想出了什么。
  3. 狂野指針和其他越界訪問:我們在這里看到的代碼中沒有任何東西,但它正在調用其他函數。 再次,運行valgrind和消毒劑。
  4. 如果之前的步驟很短,那么下一個最可能的候選者就是MSVC錯誤。 MSVC因在某些復雜代碼中弄亂事物而臭名昭着,這有點復雜。 很多時候我重新安排代碼只是為了讓MSVC開心。 有時關閉優化有幫助,有時卻沒有。 同樣嘗試不同的編譯器選項。 有時候有一個更新/補丁有幫助,有時卻沒有。 同樣適用於下一版MSVC。 我建議在調試器中查看反匯編程序,但是你說你無法訪問Windows機器。 這里最好的選擇是嘗試簡化代碼 - 使函數更小,減少參數數量。
  5. 還有其他可能的原因。 例如,堆棧可能由於某種原因搞砸了 - 可能是在與Python運行時進行交互時。 嘗試構建並運行它作為“常規”C代碼,而不是Python擴展。 消除對其他功能的調用(如果它與計算混淆,沒關系,你只是試圖找出問題)。

無論如何,我建議你在Windows機器上進行調試。 根據我的經驗,這是解決這些問題的最佳方式。

這是(錯誤)優化的影響嗎?

關閉任何優化(DEBUG?)並查看是否獲得相同的效果。

當然,如果你發現它是優化器,那么你就可以了,只能做一些事情來欺騙它,例如一個什么都不做的sprintf。

另外,printf也可以打印出指針(“%16x”,(long)&rho)而不是我認為它是不正確的,但是作為一個健全條款,以防我們缺少summat。 此外,大多數雙倍隨機位的結果通常最終在E +/- 317范圍內,因此0.000171結果有點太合理而不能完全懷疑。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM