6

私は次のテストケースに絞り込んだ奇妙な問題を抱えていました:

inl.h:

inline const char *fn() { return id; }

a.cc:

#include <stdio.h>

static const char *id = "This is A";

#include "inl.h"

void A()
{
    printf("In A we get: %s\n", fn());
}

b.cc:

#include <stdio.h>

static const char *id = "This is B";

#include "inl.h"

void B()
{
    printf("In B we get: %s\n", fn());
}

extern void A();

int main()
{
    A();
    B();
    return 0;
}

これをコンパイルすると、g++ -O1 a.cc b.cc正しく動作するようです。私は得る:

In A we get: This is A
In B we get: This is B

しかし、私がコンパイルすると、次のg++ -O0 a.cc b.ccようになります。

In A we get: This is A
In B we get: This is A

ここでは実際にC11セマンティクスを使用しようとしていますが、gccはまだC11をサポートしていないため、g++を使用しています。

私が見る限り、C11仕様とC ++仕様(C ++ 11以前の仕様-インライングローバルと静的グローバルのセマンティクスは変更されていないようです)の両方を見ると、私が望むことを実行するはずです、使用時の失敗-O0はgccのバグです。

これは正しいですか、それとも仕様のどこかに、この未定義の動作を引き起こす何かが欠けていますか?

編集

fn一般的な答えは、これが機能するために宣言する必要があると主張しているようstaticです。しかし、C99仕様の6.7.4.6(C11仕様では6.7.4.7-C ++仕様については不明)によると:

変換ユニット内の関数のすべてのファイルスコープ宣言にexternなしのインライン関数指定子が含まれている場合、その変換ユニット内の定義はインライン定義です。インライン定義は、関数の外部定義を提供せず、別の変換単位での外部定義を禁止しません。

したがって、ここには明示的なものがないためextern、これらは相互作用のない2つの独立したインライン関数である必要があります。明示的なstatic必要はありません。

明示的な静的を使用すると、Cの問題は修正されますが、C ++インラインメンバー関数では機能しませんstatic。その場合、キーワードの意味がまったく異なるためです。

4

2 に答える 2

8

1 つの定義ルールに違反しています。非静的関数fnは、2 つの翻訳単位で異なる方法で定義されています。1 つはa.ccid定義された変数にバインドし、もう 1 つはb.ccの変数にバインドします。定義はテキスト的には同一ですが、宣言された関数に例外が設定されていても、1 つの定義の規則を満たすには十分ではないため、未定義の動作が発生します。idinline

CコンパイラではなくC++コンパイラを使用しているため、C11が言うことは、C++プログラムが示す動作とは無関係です。C++ 11 では、標準 (§3.2/5) はfn、参照を許可する方法に関する規則を述べているようですid(強調と省略記号は私のものです):

各定義が異なる翻訳単位に現れ、定義が次の要件を満たす場合、プログラム内に ... 外部リンケージ (7.1.2) を持つインライン関数 ... の複数の定義が存在する可能性があります。D複数の翻訳単位で定義された名前のエンティティが与えられた場合、

  • の各定義はD、同じ一連のトークンで構成されます。と
  • の各定義ではD、対応する名前は、3.4 に従って検索され、 の定義内で定義されたエンティティをD参照するか、または同じエンティティを参照する必要があります。ただしconst 、オブジェクトが のすべての定義で同じリテラル型をD持ち、オブジェクトが定数式 (5.19) で初期化され、値 (アドレスではなく) のオブジェクトが使用され、そのオブジェクトは のすべての定義 で同じ値を持ちDます。と

の定義はfn同じ一連のトークンで構成されていますが、それらは を参照してidおり、これは 内で定義されてDおらず、両方の翻訳単位で同じエンティティではなく、すべての定義で同じ値を持っていません。C++ 標準には、内部リンケージを暗黙的に取得するインライン関数の規定がありません。C++11 §7.1.1/7 は次のように述べています。

storage-class-specifierを使用せずに名前空間スコープで宣言された名前には、以前の宣言のために内部リンケージがあり、宣言されていない場合を除き、外部リンケージがありconstます。

特定の最適化レベルで、または特定のコンパイラの特定のバージョンで期待どおりの動作が得られた場合、未定義の動作の特に悪質なバージョンを取得しているだけであり、間違っているにもかかわらず動作しているように見えます。

于 2012-06-20T19:12:30.770 に答える
3

fn()宣言されていないためstatic、@ RobKennedyが指摘しているように、あなたはUBの土地に足を踏み入れています。この特定のケースでは、-O0おそらくインライン化を無効にします。これは、関数の発行された非インライン化バージョンの1つが保持され、もう1つが破棄され、両方の呼び出しが1つの非インライン化バージョンに対して行われることを意味します。Aバージョンが常に保持されるかどうかは、コマンドラインでファイルを指定する順序、またはその他の多くのことによって異なります。-O1おそらくインライン化が含まれています。この場合、出力された関数のインライン化されていないコピーがある場合でも、2つの呼び出しはインライン化されている可能性があり、(誤って)期待される結果が得られます。

于 2012-06-20T19:33:24.297 に答える