Почему printf() позволяет этому двойнику передавать указатель?

Пара инструкций отладки printf() показывает, что указатель на двойник, который я передаю, является разыменованным на принимающей стороне, выходящим как другое значение, но только в 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 они, по сути, всегда одинаковы. В 32-битной компиляции Visual Studio они всегда одинаковы. Но когда этот код скомпилирован с Visual Studio под 64-битной архитектурой, два двойных значения отличаются друг от друга!

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 выше. Конкретный шаг компиляции для этого файла C, в случае, если проблема может быть вызвана Visual Studio или параметры компиляции, это:

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

Ни одно из этих предупреждений, из того, что я вижу, для кода, участвующего в этой конкретной головоломке, - и даже если бы они были, все, что они означали бы, это то, что значение float может стать менее точным (примерно от 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

Ответ 1

Мой быстрый взгляд на этот код не придумал ничего, что выделяется как неправильное или проблематичное. Но, когда a printf решает проблему, это означает, что существует некоторый недетерминизм. Позвольте проанализировать возможные причины:

  • Concurrency - расы данных: чаще всего, но вы говорите, что это однопоточная.
  • Неинициализированная память: rho инициализируется здесь, но, может быть, что-то в другом месте нет, и это бесполезно. Я бы запустил valgrind (в Linux) и AdressSanitizer и другие дезинфицирующие средства (должен быть доступен для clang и gcc для Windows тоже), чтобы узнать, придумали ли они что-то.
  • Дикие указатели и другой доступ вне пределов: ничего в коде, который мы видим здесь, но вызывающий другие функции. Опять же, запустить valgrind и sanitizers.
  • Если предыдущие шаги выглядят короткими, следующим наиболее вероятным кандидатом является ошибка MSVC. MSVC известен тем, что испортил вещи в некотором сложном коде, и это несколько сложно. Много раз я переставлял код, чтобы сделать MSVC счастливым. Иногда отключение оптимизации помогает, иногда это не так. То же самое для проверки различных параметров компилятора. Иногда есть обновление/патч, который помогает, иногда нет. То же самое для следующей версии MSVC. Я бы предложил посмотреть дизассемблер в отладчике, но вы говорите, что у вас нет доступа к машине Windows. Лучше всего было бы попытаться упростить код - сделать функции меньше, сократить количество аргументов.
  • Есть и другие возможные причины. Например, может быть, столкнулся по какой-то причине - возможно, при взаимодействии с временем выполнения Python. Попробуйте создать и запустить его как "обычный" код C, а не расширение Python. Устраните призывы к другим функциям (если это беспорядок с расчетами, неважно, вы просто пытаетесь выяснить проблему).

В любом случае, я рекомендую вам получить доступ к машине Windows и отладить ее. Это лучший способ понять суть таких проблем, по моему опыту.

Ответ 2

Является ли это результатом (ошибочной) оптимизации?

Отключите любую оптимизацию (DEBUG?) и посмотрите, получится ли тот же эффект.

Конечно, если вы обнаружите, что это оптимизатор, тогда вы встаете в крик и можете только сделать что-то, чтобы обмануть его, например. sprintf, который ничего не делает.

Кроме того, printf также может распечатывать указатель ( "% 16x", (long) & rho), не так, как я считаю, он неверен, но точно так же, как предложение о здравомыслии в случае отсутствия суммирования. Кроме того, результат большинства удвоений со случайными битами обычно заканчивается в диапазоне E +/- 317, поэтому результат 0,000171 является слишком разумным, чтобы быть полностью подозрительным.