5

test.cpp という次の C++ テスト プログラムがあります。

#include <cmath>
#include <iostream>

double sqrt(double d) { return std::sqrt(d); }

int main()
{
    std::cout << "sqrt(4): " << sqrt(4) << std::endl;
}

これはかなり不自然なコードであり、ご想像のとおり、Stroustrup を使って演習を行おうとしています。彼は double sqrt(double) を宣言し、読者にそれを定義してもらいたいと考えています。

g++ 4.8 (Qt 5.1 の MINGW リリースから) を使用して上記のコードをコンパイルしました。

C:\Windows\Temp>g++ -o test.exe -g test.cpp

結果の実行可能ファイルを実行すると、Windows 7 で「test.exe が動作を停止しました」と表示されました。

何が問題なのかを確認するために、GNU デバッガーで test.exe を実行しました。デバッガーのコマンドと出力:

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 名前空間の要点は、ユーザー コード内の修飾されていない名前との名前の衝突を防ぐことだと思いました。

<cmath> の GNU 実装のソース コードをのぞいてみました。ただし、チェーンのいくつかの #include をたどった後、トレイルを失いました。多分あなたはそれをもっと理解できるでしょう:

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 パズルではありません。g++ の代わりに Visual C++ コンパイラでコンパイルすると、次の警告が表示されます。

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 が動作を停止しました」。

4

2 に答える 2

6

17.6.4.3.3/2ヘッダー内の外部リンケージで宣言された各グローバル関数シグネチャは、外部リンケージでその関数シグネチャを指定するために実装に予約されています。
17.6.4.3.3/3外部リンケージで宣言された標準 C ライブラリの各名前は、名前空間とグローバル名前空間のextern "C"両方で、リンケージを含む名前として使用するために実装に予約されています。17.6.4.3.3/4外部リンケージで宣言された標準 C ライブラリの各関数シグネチャは、とリンケージの両方を含む関数シグネチャとして、またはグローバル名前空間の名前空間スコープの名前として使用するために実装に予約されています。std
extern "C"extern "C++"

于 2013-10-31T01:24:06.073 に答える
6

問題は、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:

外部リンケージで宣言された標準 C ライブラリの各名前は、名前空間とグローバル名前空間のextern "C"両方で、リンケージを含む名前として使用するために実装用に予約されています。std

外部リンケージで宣言された標準 C ライブラリの各関数シグネチャは、extern "C"extern "C++"リンケージの両方を含む関数シグネチャとして、またはグローバル名前空間の名前空間スコープの名前として使用するために、実装用に予約されています。

于 2013-10-31T01:23:01.547 に答える