C++ 叮当作响';在共享对象上导出的虚拟类上显示警告

C++ 叮当作响';在共享对象上导出的虚拟类上显示警告,c++,clang,ubsan,C++,Clang,Ubsan,我正在尝试将clang的UBSan应用于我的应用程序,该应用程序大量使用dlopen/dlsym。然而,当我应用UBSan时,出现了一些我无法完全理解的警告 带有一个头/两个文件的示例代码 福安 Foo.cpp #include "Foo.h" #include <cstddef> #define EXPORT_C extern "C" EXPORT EXPORT_C bool NewFoo(Foo** foo) noexcept; EXPORT_C void DeleteFoo

我正在尝试将clang的UBSan应用于我的应用程序,该应用程序大量使用dlopen/dlsym。然而,当我应用UBSan时,出现了一些我无法完全理解的警告

带有一个头/两个文件的示例代码

福安

Foo.cpp

#include "Foo.h"
#include <cstddef>

#define EXPORT_C extern "C" EXPORT

EXPORT_C bool NewFoo(Foo** foo) noexcept;
EXPORT_C void DeleteFoo(Foo* foo) noexcept;

class EXPORT FooImpl: public Foo {
public:
    FooImpl();
    virtual ~FooImpl() noexcept;

    virtual int bar(int value);
private:
    int val_;
};

EXPORT
FooImpl::FooImpl(): val_(1) {};

EXPORT
FooImpl::~FooImpl() noexcept {};

EXPORT
int FooImpl::bar(int value) {
    return value + val_;
}

EXPORT_C
bool NewFoo(Foo** foo) noexcept {
    if (foo == NULL) {
        return false;
    }

    try {
        *foo = new FooImpl();
    }
    catch(...) {
        return false;
    }
    return true;
}

EXPORT_C
void DeleteFoo(Foo* foo) noexcept {
    delete foo;
}

第一个和第三个错误是显而易见的:我使用了reinterpret_cast,UBSan无法理解这样的指针来自何处。但是,我不明白为什么在处理vptr时不应用
Foo
FooImpl
s继承关系。这可能是一个真正的问题还是UBSan的bug(或限制?

我相信第一个和第三个错误实际上是因为
extern“C”
函数指针和未修饰的函数指针不太一样。试着将测试中的两个用法放在
extern“C”
块中,看看这是否会消除这些错误。在函数指针类型上添加
extern“C”
不会有帮助。我相信第一个和第三个错误实际上是因为
extern“C”
函数指针和未修饰的函数指针不太一样。尝试将测试中的两个using放在
extern“C”
块中,看看这是否会消除这些错误。在函数指针类型上添加
extern“C”
将没有帮助。
#include "Foo.h"
#include <cstddef>

#define EXPORT_C extern "C" EXPORT

EXPORT_C bool NewFoo(Foo** foo) noexcept;
EXPORT_C void DeleteFoo(Foo* foo) noexcept;

class EXPORT FooImpl: public Foo {
public:
    FooImpl();
    virtual ~FooImpl() noexcept;

    virtual int bar(int value);
private:
    int val_;
};

EXPORT
FooImpl::FooImpl(): val_(1) {};

EXPORT
FooImpl::~FooImpl() noexcept {};

EXPORT
int FooImpl::bar(int value) {
    return value + val_;
}

EXPORT_C
bool NewFoo(Foo** foo) noexcept {
    if (foo == NULL) {
        return false;
    }

    try {
        *foo = new FooImpl();
    }
    catch(...) {
        return false;
    }
    return true;
}

EXPORT_C
void DeleteFoo(Foo* foo) noexcept {
    delete foo;
}
#include "Foo.h"
#include <iostream>
#include <unistd.h>
#include <dlfcn.h>

using fNewFoo_t = bool(*)(Foo**);
using fDeleteFoo_t = void(*)(Foo*);

int main() {
    Foo* foo = NULL;

    void* handle = dlopen("./libfoo.so", RTLD_LAZY);
    if (handle == NULL) {
        fprintf(stderr, "dlopen: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    fNewFoo_t fNewFoo = reinterpret_cast<fNewFoo_t>(dlsym(handle, "NewFoo"));
    fDeleteFoo_t fDeleteFoo = reinterpret_cast<fDeleteFoo_t>(dlsym(handle, "DeleteFoo"));

    if ((fNewFoo == NULL) || (fDeleteFoo == NULL)) {
        fprintf(stderr, "dlsym: %s\n", dlerror());
        dlclose(handle);
        exit(EXIT_FAILURE);
    }

    if (!fNewFoo(&foo)) {
        fprintf(stderr, "NewFoo Failed\n");
        dlclose(handle);
        exit(EXIT_FAILURE);
    }

    int i = 0;
    for (int j = 0; j < 3; j++) {
        i = foo->bar(i);
        std::cout << i << "\n";
    }
    fDeleteFoo(foo);
    dlclose(handle);

    return 0;
}
$ clang++ -fsanitize=undefined -g -fPIC -fvisibility=internal -std=gnu++11 -shared -o libfoo.so Foo.cpp
$ clang++ -fsanitize=undefined -g -std=gnu++11 -o test test.cpp -ldl
$ ./test
test.cpp:27:10: runtime error: call to function NewFoo through pointer to incorrect function type 'bool (*)(Foo **)'
Foo.cpp:31: note: NewFoo defined here
test.cpp:35:18: runtime error: member call on address 0x000001f312b0 which does not point to an object of type 'Foo'
0x000001f312b0: note: object is of type 'FooImpl'
 00 00 00 00  20 ad f2 1f 0e 7f 00 00  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  21 00 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'FooImpl'
1
2
3
test.cpp:38:5: runtime error: call to function DeleteFoo through pointer to incorrect function type 'void (*)(Foo *)'
Foo.cpp:46: note: DeleteFoo defined here