У меня есть следующая тестовая программа C ++ под названием test.cpp:
#include <cmath>
#include <iostream>
double sqrt(double d) { return std::sqrt(d); }
int main()
{
std::cout << "sqrt(4): " << sqrt(4) << std::endl;
}
Это довольно надуманный код, и, как вы уже догадались, я просто пытаюсь выполнить упражнение из Страуструпа. Он объявил double sqrt (double) и хочет, чтобы читатель определил его.
Я скомпилировал приведенный выше код, используя g ++ 4.8 (из MINGW-релиза Qt 5.1):
C:\Windows\Temp>g++ -o test.exe -g test.cpp
Когда я запустил полученный исполняемый файл, Windows 7 сказала «test.exe перестал работать».
Чтобы увидеть, что пошло не так, я запустил test.exe в отладчике GNU. Команды отладчика и вывод:
C:\Windows\Temp>gdb -q test.exe
Reading symbols from C:\Windows\Temp\test.exe...done.
(gdb) b main
Breakpoint 1 at 0x401625: file test.cpp, line 8.
(gdb) run
Starting program: C:\Windows\Temp\test.exe
[New Thread 12080.0x2ba0]
Breakpoint 1, main () at test.cpp:8
8 std::cout << "sqrt(4): " << sqrt(4) << std::endl;
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) s
sqrt (d=4) at test.cpp:4
4 double sqrt(double d) { return std::sqrt(d); }
(gdb) q
A debugging session is active.
Inferior 1 [process 12080] will be killed.
Quit anyway? (y or n) y
C:\Windows\Temp>
Из поведения и предупреждения я делаю вывод, что std :: sqrt должен вызывать sqrt из глобального пространства имен — что вызывает многократный вызов моей функции.
Было бы достаточно легко обойти нежелательную рекурсию, изменив имя моей функции sqrt или поместив ее в пространство имен. Но я хотел бы понять, почему std :: sqrt реализован таким образом, что вызывается :: sqrt. Я думал, что весь смысл пространства имен std состоит в том, чтобы предотвратить столкновение имен с неквалифицированными именами в пользовательском коде.
Я взглянул на исходный код GNU реализация <CMATH>. Тем не менее, я потерял след после нескольких включений в цепочке. Может быть, вы можете иметь больше смысла в этом:
00052 #include <math.h>
00053
00054 // Get rid of those macros defined in <math.h> in lieu of real functions.
....
00076 #undef sqrt
....
00081 namespace std
00082 {
....
00393 using ::sqrt;
00394
00395 inline float
00396 sqrt(float __x)
00397 { return __builtin_sqrtf(__x); }
00398
00399 inline long double
00400 sqrt(long double __x)
00401 { return __builtin_sqrtl(__x); }
00402
00403 template<typename _Tp>
00404 inline typename __enable_if<double, __is_integer<_Tp>::_M_type>::_M_type
00405 sqrt(_Tp __x)
00406 { return __builtin_sqrt(__x); }
....
00437 }
Кстати, это не просто головоломка GNU. Компиляция с помощью компилятора Visual C ++ вместо g ++ выдает следующее предупреждение:
C:\Windows\Temp>cl /nologo /EHsc test.cpp
test.cpp
c:\windows\temp\test.cpp(4) : warning C4717: 'sqrt' : recursive on all control
paths, function will cause runtime stack overflow
Я полагаю, что это справедливый вопрос для StackOverflow. 🙂
Запуск полученного исполняемого файла приводит к ожидаемому результату: «test.exe перестал работать».
Проблема в том, что функции, унаследованные от стандартной библиотеки C, такие как, например, <cmath>
функции — несколько забавные звери: они сделаны так, как будто они живут в пространстве имен std
но на самом деле они extern "C"
функции, живущие в глобальном пространстве имен. В основном зовет std::sqrt(x)
эффективно звонки ::sqrt(x)
которая является функцией, которую вы только что определили!
Я не проверял, что говорит стандарт C ++ об этих именах в глобальном пространстве имен, но я был бы совершенно уверен, что он классифицирует их как зарезервированные имена. То есть вам лучше не определять ::sqrt
в любой форме или форме. Определите функцию в подходящем пространстве имен, и все будет в порядке.
ОК, я проверил Соответствующий пункт 17.3.24 [defns.reserved.function]:
зарезервированная функция
функция, указанная как часть стандартной библиотеки C ++, которая должна быть определена реализацией [Примечание: если программа C ++ предоставляет определение для любой зарезервированной функции, результаты не определены. —Конечная записка]
… и 17.6.4.3.3 [extern.names], пункты 3 и 4:
Каждое имя из библиотеки Standard C, объявленной с внешней связью, зарезервировано для реализации для использования в качестве имени с
extern "C"
связь, как в пространстве именstd
и в глобальном пространстве имен.Каждая сигнатура функции из библиотеки Standard C, объявленная с помощью внешней связи, зарезервирована для реализации для использования в качестве сигнатуры функции с обоими
extern "C"
а такжеextern "C++"
связь, или как имя области имен пространства в глобальном пространстве имен.
17.6.4.3.3 / 2 Каждая подпись глобальной функции, объявленная с внешней связью в заголовке, зарезервирована для реализации, чтобы обозначить эту подпись функции внешней связью.
17.6.4.3.3 / 3 Каждое имя из библиотеки Standard C, объявленной с внешней связью, зарезервировано для реализации для использования в качестве имени сextern "C"
связь, как в пространстве именstd
и в глобальном пространстве имен.
17.6.4.3.3 / 4 Каждая сигнатура функции из библиотеки Standard C, объявленная с помощью внешней связи, зарезервирована для реализации для использования в качестве сигнатуры функции с обоимиextern "C"
а такжеextern "C++"
связь, или как имя области имен пространства в глобальном пространстве имен.