web-dev-qa-db-ja.com

cでvoid *を使用してジェネリック関数を作成するにはどうすればよいですか?

値を_1_ずつインクリメントするincr関数があります。同じ機能に対して異なる関数を作成したくないので、汎用にしたいです。

intfloatcharを_1_だけインクリメントしたいとします。

_void incr(void *vp)
{
        (*vp)++;
}
_

しかし、私が知っている問題は_Dereferencing a void pointer is undefined behaviour_です。エラーが発生する場合があります:_Invalid use of void expression_。

私のmain関数は:

_int main()
{

int i=5;
float f=5.6f;
char c='a';

incr(&i);
incr(&f);
incr(&c);

return 0;
}
_

問題はこれをどのように解決するかです。 Conlyでそれを解決する方法はありますか

または

データ型ごとにincr()を定義する必要がありますか?はいの場合、_void *_の用途は何ですか

swap()sort()の同じ問題。同じ関数ですべての種類のデータ型を交換して並べ替えたい。

16
Omkant

最初のものをマクロとして実装できます。

#define incr(x) (++(x))

もちろん、注意しないと、これは不快な副作用を引き起こす可能性があります。これは、Cがさまざまなタイプのいずれかに同じ操作を適用するために提供する唯一の方法です。特に、マクロはテキスト置換を使用して実装されているため、コンパイラがマクロを認識するまでに、リテラルコード++whatever;があり、使用したアイテムのタイプに++を適切に適用できます。提供されます。 voidへのポインタがあると、実際の型について(もしあれば)あまり知らないので、そのデータを直接操作することはできません)。

void *は通常、問題の関数が関連するデータの正確なタイプを実際に知る必要がない場合に使用されます。場合によっては(たとえば、qsort)、データの詳細を知る必要がないようにコールバック関数を使用します。

ソートとスワップの両方を行うので、qsortをもう少し詳しく見てみましょう。その署名は次のとおりです。

void qsort(void *base, size_t nmemb, size_t size,
           int(*cmp)(void const *, void const *));

したがって、最初はあなたが尋ねたvoid *です-ソートされるデータへのポインタ。 2番目はqsortに配列内の要素の数を伝えます。 3つ目は、配列内の各要素のサイズです。最後は、個々の項目を比較できる関数へのポインターであるため、qsortはその方法を知る必要はありません。たとえば、qsort内のどこかに、次のようなコードがあります。

// if (base[j] < base[i]) ...
if (cmp((char *)base+i, (char *)base+j) == -1)

同様に、2つのアイテムを交換するために、通常、一時ストレージ用のローカル配列があります。次に、バイトをarray[i]からそのtempにコピーし、次にarray[j]からarray[i]に、最後にtempからarray[j]にコピーします。

char temp[size];

memcpy(temp, (char *)base+i, size);              // temp = base[i]
memcpy((char *)base+i, (char *)base+j, size);    // base[i] = base[j]
memcpy((char *)base+j, temp, size);              // base[j] = temp
19
Jerry Coffin

void *を使用しても、ポリモーフィックな動作は得られません。これは、あなたが探しているものだと思います。 void *を使用すると、ヒープ変数の型チェックをバイパスできます。実際のポリモーフィックな動作を実現するには、型情報を別の変数として渡し、incr関数でそれを確認してから、渡すことで目的の型ORへのポインターをキャストする必要があります。関数ポインタとしてのデータに対する操作(他の人は例としてqsortに言及しています)。 Cには言語に組み込まれた自動ポリモーフィズムがないため、それをシミュレートするのはあなた次第です。舞台裏では、ポリモーフィズムを組み込んだ言語が舞台裏でこのようなことをしています。

詳述すると、void *は、int、float、stringなどの一般的なメモリブロックへのポインタです。メモリブロックの長さは、ましてやポインタに格納されません。データのタイプ。内部的には、すべてのデータはビットとバイトであり、本質的にビットとバイトはタイプレスであるため、タイプは実際には論理データが物理的にエンコードされる方法の単なるマーカーであることに注意してください。 Cでは、この情報は変数とともに格納されないため、コンパイラに自分で提供する必要があります。これにより、ビットシーケンスを2の補数整数として扱う操作を適用するかどうかがわかり、IEEE754倍精度浮動小数点ASCII文字データ、関数など。これらはすべて、さまざまなタイプのデータの形式と操作の特定の標準です。 void *を特定の型へのポインターにキャストすると、youプログラマーは、ポイントされたデータが実際にはキャスト先のタイプ。そうでなければ、あなたはおそらく奇妙な行動をしているでしょう。

では、void *は何に役立つのでしょうか?タイプに関係なくデータのブロックを処理するのに適しています。これは、メモリの割り当て、コピー、ファイル操作、関数へのポインタの受け渡しなどに必要です。ただし、ほとんどすべての場合、Cプログラマーは、組み込みの操作を持つ型を使用してデータを構造化することにより、この低レベルの表現から可能な限り抽象化します。または、構造体を使用して、これらの構造体に対する操作をプログラマーが関数として定義します。

詳細については、 ウィキペディアの説明を確認してください をお勧めします。

13
acjay

あなたが求めていることを正確に行うことはできません-インクリメントのような演算子は特定のタイプで動作する必要があります。だから、あなたはcould次のようなことをします:

enum type { 
    TYPE_CHAR,
    TYPE_INT,
    TYPE_FLOAT
};

void incr(enum type t, void *vp)
{
    switch (t) {
        case TYPE_CHAR:
        (*(char *)vp)++;
        break;

        case TYPE_INT:
        (*(int *)vp)++;
        break;

        case TYPE_FLOAT:
        (*(float *)vp)++;
        break;
    }
}

次に、次のように呼びます。

int i=5;
float f=5.6f;
char c='a';

incr(TYPE_INT, &i);
incr(TYPE_FLOAT, &f);
incr(TYPE_CHAR, &c);

もちろん、これは、個別のincr_int()incr_float()、およびincr_char()関数を定義するだけでは何も得られません。これはvoid *の目的ではありません。

void *の目的は、作成しているアルゴリズムがオブジェクトの実際のタイプを気にしない場合に実現されます。良い例は、次のように宣言されている標準の並べ替え関数qsort()です。

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));

これは、任意のタイプのオブジェクトの配列を並べ替えるために使用できます。呼び出し元は、2つのオブジェクトを比較できる比較関数を提供するだけで済みます。

swap()関数とsort()関数の両方がこのカテゴリに分類されます。 swap()はさらに簡単です-アルゴリズムは、オブジェクトを交換するためにオブジェクトのサイズ以外のことを知る必要はありません。

void swap(void *a, void *b, size_t size)
{
    unsigned char *ap = a;
    unsigned char *bp = b;
    size_t i;

    for (i = 0; i < size; i++) {
        unsigned char tmp = ap[i];

        ap[i] = bp[i];
        bp[i] = tmp;
    }
}

これで、任意の配列が与えられると、その配列内の2つのアイテムを交換できます。

int ai[];
double ad[];

swap(&ai[x], &ai[y], sizeof(int));
swap(&di[x], &di[y], sizeof(double));
4
caf

「汎用」スワップの使用例

このコードは、2ブロックのメモリを交換します。

void memswap_arr(void* p1, void* p2, size_t size)
{      
      size_t         i;
      char* pc1= (char*)p1;
      char* pc2= (char*)p2;
      char  ch;

      for (i= 0; i<size; ++i) {
        ch=     pc1[i];
        pc1[i]= pc2[i];
        pc2[i]= ch;
      }
}

そして、あなたはそれをこのように呼びます:

int main() {
     int i1,i2;
     double d1,d2;
     i1= 10; i2= 20;
     d1= 1.12; d2= 2.23;
     memswap_arr(&i1,&i2,sizeof(int));     //I use memswap_arr to swap two integers
     printf("i1==%d i2==%d \n",i1,i2);     //I use the SAME function to swap two doubles
     memswap_arr(&d1,&d2,sizeof(double));      
     printf("d1==%f d2==%f \n",d1,d2);
     return 0;
}

これにより、1つの関数をさまざまなデータ型に使用する方法がわかるはずです。

1
Maroun

逆参照する前に、ポインタを具象型にキャストする必要があります。したがって、ポインタ変数のタイプを渡すコードも追加する必要があります。

0
Maciej

これが幅広い質問への非回答として外れる可能性がある場合は申し訳ありません "cでvoid *を使用してジェネリック関数を作成する方法?" ..しかし、あなたが抱えていると思われる問題(の変数をインクリメントする任意の型、および未知の型の2つの変数の交換)は、関数やvoidへのポインターよりもマクロを使用して行う方がはるかに簡単です。

インクリメントは十分に簡単です:

_#define increment(x) ((x)++)
_

スワッピングの場合、私は次のようなことをします:

_#define swap(x, y)                  \
({                                  \
        typeof(x) tmp = (x);        \
        (x) = (y);                  \
        (y) = tmp;                  \
})
_

...これは、私のテストに基づいて、int、double、およびcharポインター(文字列)に対して機能します。

インクリメントマクロはかなり安全なはずですが、スワップマクロはtypeof()演算子に依存しています。これはGCC/clang拡張であり、標準Cの一部ではありません(gccまたはclangでコンパイルするだけの場合はこれはそれほど問題にはならないはずです)。

私はその種が元の質問をかわしたことを知っています。しかし、うまくいけば、それでも元の問題が解決します。

0
Saeed Baig

タイプジェネリック機能(C11標準)を使用できます。より高度な数学関数(_++_演算子よりも高度な)を使用する場合は、_<tgmath.h>_および_<math.h>_および__(の関数の型ジェネリック定義である_<complex.h>_に移動できます。 SOMECODE)__。

__Generic_キーワードを使用して、型ジェネリック関数をマクロとして定義することもできます。例の下:

_#include <stdio.h>

#define add1(x) _Generic((x), int: ++(x), float: ++(x), char: ++(x), default: ++(x))

int main(){
  int i = 0;
  float f = 0;
  char c = 0;

  add1(i);
  add1(f);
  add1(c);

  printf("i = %d\tf = %g\tc = %d", i, f, c);
}
_

言語標準 およびより洗練された例の詳細については、 Robのプログラミングブログ からのこの投稿を参照してください。

_* void_、スワップおよびソートの質問については、 Jerry Coffin の回答を参照してください。

0
jlaraval