18

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

、、をインクリメントしたいとしますintfloatchar1

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;
}

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

また

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

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

4

7 に答える 7

21

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

#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]をその一時に、次にからarray[j]array[i]、最後にからにコピーtemparray[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
于 2012-11-20T09:03:06.130 に答える
13

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

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

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

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

于 2012-11-20T08:54:44.070 に答える
7

あなたが求めていることを正確に行うことはできません-incrementのような演算子は特定のタイプで動作する必要があります。したがって、次のようなことができます。

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));
于 2012-11-20T09:01:21.760 に答える
2

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

このコードは、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つの関数をさまざまなデータ型に使用する方法がわかるはずです。

于 2012-11-20T08:51:14.117 に答える
1

これが「 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でコンパイルするだけの場合、これはそれほど多くないはずです)問題の)。

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

于 2017-11-05T08:52:22.660 に答える
1

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

_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、スワップ、ソートの質問については、JerryCoffinの回答を参照してください

于 2018-01-23T14:16:20.940 に答える
0

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

于 2012-11-20T08:54:28.023 に答える