1

非常に単純なc++コードを作成しました。ここでは、std::sqrtを呼び出すだけのsqrtという関数を定義しました。予期せず、セグメンテーション違反が発生しました。関数sqrtの名前を別の名前に変更しても、問題は発生しません。ただし、定義したsqrt関数が名前空間stdにないため、名前の競合は見られません。したがって、2つは完全に分離されている必要があります。では、問題の本当の原因は何ですか?ありがとう!

#include<iostream>
#include<cmath>

double sqrt(double d);

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

int main() {
    double x = 3.0;
    std::cout << "The square root of " << x << " is " << sqrt(x) << '\n';
    return 0;
}
4

3 に答える 3

4

<cmath>面白いヘッダーです。同義語::sqrtを 作成することは許可されています(必須ではありません) 。std::sqrtそれを含める場合は、両方が存在すると想定するのが最善です(または、単に含める<math.h>場合は、::sqrt取得する必要があるすべてです)。あなたの場合におそらく起こっていることは、1)実際には(を介して)std::sqrtの同義語であり、2)リンカーが最初のものを取得しているため、無限の再帰が発生することです。名前を変更する以外の唯一の解決策は 、名前空間に自分を配置することです。using::sqrt::sqrtsqrt

編集:

明確にするために:上記はC++11です。以前のバージョンのC++では、グローバル名前空間に何も導入できませんでした。<cmath>ただし、すべての実装で実行されたため、プラクティスを祝福するために標準が変更されました。(これは、コンパイラーを標準に準拠させる1つの方法だと思います。)

編集:

コメントの質問に答えて、ライブラリがシンボルを「ピックアップ」する方法に関するいくつかの追加情報。正式には、C ++標準によれば、プログラム内に同じ関数(同じ名前、名前空間、および引数タイプ)の2つの定義がない場合があります。2つの定義が別々の翻訳単位にある場合、動作は定義されていません。これを念頭に置いて、いくつかの実用的な考慮事項があります。

最初のものは、ライブラリの定義(または少なくとも従来の定義)と見なすことができます。ライブラリは、モジュールのセットであり、標準では翻訳ユニットです。(通常、ただし常にではありませんが、モジュールはコンパイルされたオブジェクトファイルで構成されます。)ただし、ライブラリにリンクしても、ライブラリ内のすべてのモジュールが取り込まれるわけではありません。ライブラリのモジュールは、未解決の外部を解決する場合にのみプログラムに組み込まれます。したがって、::sqrtリンカがライブラリを調べる前にがすでに定義(解決)されている場合、ライブラリに含まれるモジュールは ::sqrtプログラムの一部にはなりません。

実際には、ライブラリという用語は近年乱用されており、その意味が変わったと言えるほどです。特に、Microsoftが「動的にロードされたライブラリ」と呼んでいるもの(および以前はUnixで「共有オブジェクト」と呼ばれていたもの)は、従来の意味でのライブラリではなく、上記はそれらに当てはまりません。ただし、動的ローダーがどのように機能するかによっては、他の問題も発生します。Unixの場合、複数の共有オブジェクトが同じシンボルを持っていると、すべてが最初にロードされたものに解決されます(デフォルトでは、これはに渡されるオプションによって制御できますdlopen)。Windowsの場合、デフォルトでは、シンボルは可能であればDLL内で解決されます。あなたの場合、std::sqrtがインライン関数であるか、として指定されているusing ::sqrt場合、これはを呼び出すDLLになります std::sqrt; ヘッダーにある場合__declspec(dllexport)、これはの実装を含むDLLになります std::sqrt

最後に、今日のほとんどすべてのリンカーは、何らかの形の弱参照をサポートしています。これは通常、テンプレートのインスタンス化に使用されます。std::vector<int>::vector( size_t, int )たとえば、それを使用するすべての翻訳単位でインスタンス化されますが、「弱い」記号として使用されます。次に、リンカは1つを選択し(おそらく最初に遭遇しますが、指定されていません)、他のすべてを破棄します。この手法は主にテンプレートのインスタンス化に使用されますが、コンパイラーは弱参照を使用して任意の関数を定義できます(関数がインラインの場合は定義します)。この場合、定義が異なる場合( ::sqrt)、このプログラムは1つの定義規則に違反しているため、違法であると言えます。ただし、結果は未定義の動作であり、診断は必要ありません。たとえば、インライン関数または関数テンプレートを2つの異なる変換単位で異なる方法で定義した場合、エラーが発生することはほとんどありません。コンパイラが実際にそれらをインライン化しない場合、リンカは1つを選択し、両方の変換ユニットでそれを使用します。あなたの場合(::sqrt)、これが当てはまるとは思えません。これは実際のライブラリ関数であり、インラインではないと思います。(インライン化されている場合、定義はヘッダー<cmath>にあり、両方の定義が同じ翻訳単位にあるため、重複した定義エラーが発生します。)

于 2013-03-24T21:29:06.697 に答える
2

問題は、名前(名前空間なし)と。<cmath>を持ち込むことであるようです。別の名前を使用する必要があります。sqrtstd::std::sqrt

GCC 4.8のスナップショットを使用して、次の例を参照してください。

#include<iostream>
#include<cmath>

int main() {
    double x = 9.0;
    std::cout << sqrt(x) << '\n'; // look, no std::sqrt
}
于 2013-03-24T21:23:48.230 に答える
1

パラグラフ17.6.1.2/4による:

条項18から30および付録Dに記載されている場合を除き、各ヘッダーcnameの内容は、C標準ライブラリ(1.2)またはC Unicode TRで指定されている、対応するヘッダー名.hの内容と同じである必要があります。 、包含によるかのように。ただし、C ++標準ライブラリでは、宣言(Cでマクロとして定義されている名前を除く)は、名前空間stdの名前空間スコープ(3.3.6)内にあります。これらの名前が最初にグローバル名前空間スコープ内で宣言され、次に明示的なusing-declarations(7.3.3)によって名前空間stdに挿入されるかどうかは指定されていません。

また、Annex D.5 / 2によると:

それぞれがname.hという形式の名前を持つすべてのCヘッダーは、対応するcnameヘッダーによって標準ライブラリ名前空間に配置された各名前がグローバル名前空間スコープ内に配置されているかのように動作します。これらの名前が最初に名前空間stdの名前空間スコープ(3.3.6)内で宣言または定義され、次に明示的なusing-declarations(7.3.3)によってグローバル名前空間スコープに挿入されるかどうかは指定されていません。

グローバル関数を使用可能にするために使用される正確な手法は実装に任されているため、実装にはおそらく名前空間using内に次のようなディレクティブがあります。std

namespace std
{
    using ::sqrt;

    // ...
}

これは、std::sqrt実際にはのエイリアスになることを意味し、効果的に自分自身を再帰的に呼び出すこと::sqrtになる定義を提供します。::sqrt

唯一の解決策は、別の名前を選択することです。

于 2013-03-24T21:32:41.763 に答える