2

次のサンプルコードがあります。

@interface S1 : NSObject
{
    void(*fn_)();
}
@end
@implementation S1
- (void) set:(BOOL)f
{
    if (f)
    {
        struct A { static void f() { std::cout << "1" << std::endl; } };
        fn_ = A::f;
    }
    else
    {
        struct A { static void f() { std::cout << "2" << std::endl; } };
        fn_ = A::f;
    }
}
- (void) test { fn_(); }
@end

struct S2
{
    void set(BOOL f)
    {
        if (f)
        {
            struct A { static void f() { std::cout << "1" << std::endl; } };
            fn_ = A::f;
        }
        else
        {
            struct A { static void f() { std::cout << "2" << std::endl; } };
            fn_ = A::f;
        }
    }
    void test() { fn_(); }
    void(*fn_)();
};

int main(int argc, const char * argv[])
{
    auto s1 = [[S1 alloc] init];
    [s1 set:TRUE];
    [s1 test];
    [s1 set:FALSE];
    [s1 test];

    S2 s2;
    s2.set(TRUE);
    s2.test();
    s2.set(FALSE);
    s2.test();

    return 0;
}

印刷します

1
1
1
2

しかし、私は期待しています

1
2
1
2

2番目の構造体の名前を別の名前(「B」など)に変更すると、常に期待どおりに機能します。

警告は表示されないため、プログラムが正しく機能しない理由を見つけるのは困難です。

それは私の無知ですか、それともllvmのバグですか?

4

2 に答える 2

2

それは確かにclangバグのように見えます。(ただし、Objective-C++ 言語を指定する標準は存在しないため、何が「正しい」かは少し不明です)

実行可能コードにコンパイルする代わりに、次のようなコマンドを使用して LLVM IR を生成する場合

clang -g -std=c++11  -Wall -Wextra localstruct.mm  -emit-llvm -S

メソッドは-set:これにコンパイルされ、強調表示された 2 つの関数ポインター割り当て行のように見えます。

define internal void @"\01-[S1 set:]"(%0* %self, i8* %_cmd, i8 signext %f) uwtable ssp {
  %1 = alloca %0*, align 8
  %2 = alloca i8*, align 8
  %3 = alloca i8, align 1
  store %0* %self, %0** %1, align 8
  call void @llvm.dbg.declare(metadata !{%0** %1}, metadata !1490), !dbg !1491
  store i8* %_cmd, i8** %2, align 8
  call void @llvm.dbg.declare(metadata !{i8** %2}, metadata !1492), !dbg !1491
  store i8 %f, i8* %3, align 1
  call void @llvm.dbg.declare(metadata !{i8* %3}, metadata !1493), !dbg !1494
  %4 = load i8* %3, align 1, !dbg !1495
  %5 = icmp ne i8 %4, 0, !dbg !1495
  br i1 %5, label %6, label %12, !dbg !1495

; <label>:6                                       ; preds = %0
  %7 = load %0** %1, align 8, !dbg !1497
  %8 = load i64* @"OBJC_IVAR_$_S1.fn_", !dbg !1497, !invariant.load !1499
  %9 = bitcast %0* %7 to i8*, !dbg !1497
  %10 = getelementptr inbounds i8* %9, i64 %8, !dbg !1497
  %11 = bitcast i8* %10 to void ()**, !dbg !1497

store void ()* @"_ZN10-[S1 set:]1A1fEv", void ()** %11, align 8, !dbg !1497

  br label %18, !dbg !1500

; <label>:12                                      ; preds = %0
  %13 = load %0** %1, align 8, !dbg !1501
  %14 = load i64* @"OBJC_IVAR_$_S1.fn_", !dbg !1501, !invariant.load !1499
  %15 = bitcast %0* %13 to i8*, !dbg !1501
  %16 = getelementptr inbounds i8* %15, i64 %14, !dbg !1501
  %17 = bitcast i8* %16 to void ()**, !dbg !1501

store void ()* @"_ZN10-[S1 set:]1A1fEv", void ()** %17, align 8, !dbg !1501

  br label %18

; <label>:18                                      ; preds = %12, %6
  ret void, !dbg !1503
}

どちらの場合も、同じ関数記号を参照しているように見えます_ZN10-[S1 set:]1A1fEv。構造体のメソッドに対応するコードを見ると、 と の 2 つを参照してい_ZZN2S23setEaEN1A1fEvます_ZZN2S23setEaEN1A1fE_0v

FWIW、GCC の Objective-C++ コンパイラは、望ましい結果を生成します。コード内の問題を回避するだけでなく、clang プロジェクトにバグを報告してください。

于 2012-10-29T18:11:24.503 に答える
1

このコードは、実際にスタック上にある構造体の内容を指すようにインスタンス変数を割り当てているように見えます。

あなたが見ているアウトプットは偶然にすぎません。最適化をオンにしたり、あらゆる種類の関数呼び出しの複雑さがスローされたりすると、クラッシュする可能性があります。

推測ですが、私の C++ は少し錆びています。

于 2012-09-27T21:13:20.433 に答える