0

これに対する答えはわかっていますが、それを分析して楽しむことができます。そして、私たちは楽しみながら学びます!

これらのテストには gcc 4.1.2 を使用しました。

まず第一に、インライン関数は異なる翻訳単位で異なる定義を持つため、このコードは標準ではありません。そんなこと知ってる。しかし、何が起こっているのかを分析して、私が作る 3 つの質問に答えてみましょう。私たちはそれから学びます:)

ファイルをシンプルに保ちます (たとえば、#ifndef ガードはありません)。

これらのファイルがあるとします:

インクリメント.h :

inline int increment()
{
    static int value = 0;
    return ++value;
}

decrement.h :

int decrement();

decrement.cpp :

inline int increment()
{
    static int value = 0;
    return --value; // Attention to this
}

int decrement()
{
    return increment();
}

main.cpp :

#include <iostream>
#include "increment.h"
#include "decrement.h"

using namespace std;

int main()
{
    cout << increment() << endl;
    cout << increment() << endl;
    cout << decrement() << endl;
}

この Makefile でそれらをコンパイルすると:

CC=gcc
CFLAGS=-I. -O2

crazy: main.o decrement.o
        $(CC) -lstdc++ main.o decrement.o -o crazy

main.o: main.cpp increment.h decrement.h
        $(CC) $(CFLAGS) -c main.cpp -o main.o

decrement.o: decrement.cpp decrement.h
        $(CC) $(CFLAGS) -c decrement.cpp -o decrement.o

clean:
        rm -f *.o *.~ crazy

出力は次のとおりです。

1
2
1

Makefile から -O2 フラグを削除すると、次のようになります。

  CFLAGS=-I.

出力は次のとおりです。

1
2
3

main.o と decrement.o の順序も変更した場合 (先ほど行ったように -O2 フラグを付けずに残します):

$(CC) -lstdc++ decrement.o main.o -o crazy

結果は次のとおりです。

-1
-2
-3

ここで何が起こっているのですか?-O2 フラグとオブジェクト ファイルのリンク順序によって、出力がこのように変わるのはなぜですか?

4

1 に答える 1

0

ここには、main.cpp と decrement.cpp の 2 つの翻訳単位があります。

プリプロセッサ パスの後に main.cpp と decrement.cpp がどのように見えるかを確認するために、increment.h と decrement.h の #include ディレクティブを置き換えてみましょう (他のインクルードは置き換えません)。

decrement.cpp :

inline int increment()
{
    static int value = 0;
    return --value; // Attention to this
}

int decrement()
{
    return increment();
}

main.cpp :

#include <iostream>

inline int increment()
{
    static int value = 0;
    return ++value;
}

int decrement();

using namespace std;

int main()
{
    cout << increment() << endl;
    cout << increment() << endl;
    cout << decrement() << endl;
}

decrement.cppの場合、リンカーに似たシンボルをエクスポートします。

(inline) int increment::value = 0;
inline int increment(); // exported only if -O2 flag is NOT specified, with -O2 it is inlined
int decrement();

main.cppの場合、以下をエクスポートします。

int (inline) increment::value = 0;
inline int increment(); // exported only if -O2 flag is NOT specified, with -O2 it is inlined
int main();

インクリメントが実際にインライン化されている (-O2 フラグが設定されている) 場合、"int::increment value = 0" がリンカによってどこかに定義され、それらの変換単位は次のようになります。

デクリメント.cpp

int decrement()
{
    return --increment::value;
}

main.cpp

#include <iostream>

int decrement();

using namespace std;

int main()
{
    cout << ++increment::value << endl;
    cout << ++increment::value << endl;
    cout << decrement() << endl;
}

したがって、次の動作が得られます。

1
2
1

ただし、-O2 フラグを削除すると、コンパイラは関数をインライン化せず、代わりに本体を作成します (デバッガーにはその関数のブレークポイントを配置するための固有の場所があるため、デバッグが容易になります) )。したがって、「inline int increment()」に対して 2 つのボディが作成されます。1 つは decrement.cpp (decrement.o) にあり、値を減らします。もう 1 つは main.cpp (main.o) にあり、値を増やします。

関数の本体は 1 回だけ定義する必要があるため、リンク時に "int increment()" がインラインでない場合、複数の本体定義がリンカー エラーにつながります。ただし、インラインであるため、リンカーは「inline int increment()」のすべての本体がすべての翻訳単位で同じであると想定します。標準では、すべてのインライン関数はどこでも同じ本体を持たなければならないため、リンカーは最初の彼ら。

リンクする場合:

$(CC) -lstdc++ main.o decrement.o -o crazy

最初の本体は、値をインクリメントする main.o 内のものです。したがって、次のようになります。

1
2
3

ただし、リンクする場合:

    $(CC) -lstdc++ decrement.o main.o -o crazy

リンカは実際に値を減分する decrement.o から本体を選択します。したがって、次のようになります。

-1
-2
-3

質問: 一方のボディ (main.o) が値を 0 に初期化し、もう一方 (decrement.o) がそれを値 = 3 などに初期化するとどうなるでしょうか? どう思いますか?

于 2013-12-05T19:57:31.753 に答える