12

私は in を使用dlsym()Cていますが、の戻り値をdlsym()明示的にキャストする必要があるのか​​、それとも暗黙的に正しくキャストするのかという質問があります。関数は次のとおりです。

double (*(compile)(void))(double x, double y)
{
    if (system("scan-build clang -fPIC -shared -g -Wall -Werror -pedantic "
           "-std=c11 -O0 -lm foo.c -o foo.so") != 0) {
        exit(EXIT_FAILURE);
    }

    void *handle;
    handle = dlopen("./foo.so", RTLD_LAZY);
    if (!handle) {
        printf("Failed to load foo.so: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    foo f;
    f = (foo)dlsym(handle, "foo");
    if (!f) {
        printf("Failed to find symbol foo in foo.so: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    return f;
}

この関数compile()は値をとらず、double入力として 2 つの を受け取り、double を返す関数へのポインターを返します。次に、共有オブジェクトをコンパイルするシステム コールを設定しますfoo.so。次に、foo.soで開きdlopen()ます。次に、ヘッダーで次のように定義したタイプのオブジェクトをdlsym()見つけfooて返します。foo.sofoo

typedef double (*foo)(double, double);

キャストする必要がありdlsym()ますか?

4

2 に答える 2

17

C 標準は、さまざまなオブジェクト型へのポインター、特にオブジェクト型ではなく関数へのポインターが異なる表現を持つ可能性があることを前提として書かれています。そのため、一般に、ポインターを混在させたくない場合、または混在させた場合、最新のコンパイラーは警告を表示します。警告を黙らせたい場合は、通常、明示的なキャストを使用します。

dlsym一方、オブジェクト ファイル内の任意のデータ型 (オブジェクトまたは関数)への任意のポインターを返すことができるはずなので、その存在自体がすべてのポインターがほぼ同じであると想定しています。

言い換えれば、 を使用するコードはdlsym、すべてのポインタが安全に相互変換できるマシンに「のみ」移植可能であるという意味で、移植性が広くないという意味で、本質的に移植性がありません。(もちろん、これは今日の一般的なマシンのほぼすべてです。)

したがって、はい、警告を黙らせるためにポインターをキャストする必要があります。そうすることで、すべてのポインターが同じではないマシンへのコードの移植性が低下する可能性がありますあなたのコードは動作しないことを通知します) dlsym、とにかくそれらのマシンで動作することはありません (または現在の形式で存在することさえありません)。

(そして、gcc -pedanticから関数ポインタ型への明示的なキャストについてさえ警告する場合は、別のバージョンの に切り替えるか、もちろん を使用void *しない以外にできることはあまりありません。)gcc-pedantic


補遺: 私の答えは、異なるタイプのデータへのポインター間の変換が問題になる可能性があるように聞こえましたが、通常は問題ありません。型void *は汎用データ ポインターとして適切に定義されています。それがmalloc返されるものであり、任意のオブジェクト ポインター型に静かに変換できるように定義されています。つまり、キャストが必要になることさえありません。したがって、関数ポインターのわずかな問題を 除けdlsymば、の戻り値の型としてはほぼ適切な選択です。この問題はありません (関数へのポインターを malloc しようとすることはほとんどありません) が、常にこの問題があります (動的に読み込まれるオブジェクト ファイルで一般的にアクセスしようとしているシンボルは、少なくとも同じ頻度でコード化されています)。再データ)。しかし、関数ポインタは何ですかmallocdlsymvoid *への変換が保証されていないため、警告が表示される可能性が非常に高くなります。これがキャストが必要な理由であり、キャストを-pedantic使用しても警告が表示される可能性があります。

于 2015-07-20T23:16:31.153 に答える
12

dlsym()値を返しvoid*ます。このポインター値は、オブジェクトまたは関数のいずれかを参照できます。

オブジェクトを指している場合、C はvoid*任意のポインターからオブジェクトへの型への暗黙的な変換を定義しているため、キャストは必要ありません。

int *ptr = dlsym(handle, "name");

関数を指している場合 (おそらくもっと一般的です)、void*関数へのポインタ型への暗黙的な変換は行われないため、キャストが必要です。

標準 C では、void*値を意味のある関数ポインターに変換できるという保証はありません。を定義する POSIX は、ターゲットが対応する関数の正しい型である限り、によって返される値を関数へのポインタ型に意味のある形で変換できるdlsym()ことを暗黙的に保証します。void*dlsym()

パラメータのない void 関数を扱っていると仮定します。

typedef void (*func_ptr_t)(void);
func_ptr_t fptr = (func_ptr_t)dlsym(handle, "name");

たまたま、gcc ( with -pedantic) はこれについて警告します:

warning: ISO C forbids conversion of object pointer to function pointer type

この警告は厳密には正しくありません。ISO C では、オブジェクト ポインターを関数ポインター型に変換することは実際には禁止されていません。標準には、キャスト演算子によって違反されない可能性があるいくつかの制約がリストされています。void*関数ポインター型への変換はその 1 つではありません。C 標準では、このような変換の動作は定義されていませんが、この特定のケースでは POSIX が定義しているため、これは問題ではありません。

Steve Summit の回答に対するコメントは、次のことを示唆しています。

*(void **) (&f) = dlsym(handle, "foo");

gcc の警告を黙らせます。それは可能ですが、C または POSIX によって保証されていない仮定を行います。POSIX は、 の結果が関数ポインタに変換さdlsymれることを保証します。同じ表現または配置があることを保証するものではありません。動作する可能性がありますが、お勧めしません。

于 2015-07-21T00:55:01.440 に答える