Cプログラミングにおけるvoidポインターの概念


129

Cプログラミング言語で型キャストせずにvoidポインターを逆参照することは可能ですか?

また、ポインタを受け取り、それをvoidポインタに格納できる関数を一般化する方法はありますか?そのvoidポインタを使用して、一般化された関数を作成できますか?

たとえば:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

if-elseステートメントを使用せずにこの関数をジェネリックにしたいのですが、これは可能ですか?

また、voidポインターの概念を説明する優れたインターネット記事がある場合は、URLを提供できれば有益です。

また、voidポインターを使用したポインター演算は可能ですか?

回答:


97

Cプログラミング言語で型キャストせずにvoidポインターを逆参照することは可能ですか?

いいえ、voidタイプがないことを示します。これは、逆参照したり割り当てたりできるものではありません。

ポインターを受け取り、それをvoidポインターに格納できる関数を一般化する方法があり、そのvoidポインターを使用して、一般化された関数を作成できます。

適切に位置合わせされていない可能性があるため、移植可能な方法で単に逆参照することはできません。これは、ARMなどの一部のアーキテクチャで問題になる可能性があります。データ型へのポインタは、データ型のサイズの境界で整列する必要があります(たとえば、32ビット整数へのポインタは、逆参照するには4バイト境界で整列する必要があります)。

例えば、読んでuint16_tからvoid*

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

また、無効なポインタを使用したポインタ演算は可能ですか...

ポインターのvoid下に具体的な値がないため、ポインターのポインター演算ができないため、ポインターのサイズを指定できません。

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */

2
私が誤って思い出さない限り、2つの方法でvoid *を安全に逆参照することができます。char *へのキャストは常に許容され、元の型がわかっている場合は、その型にキャストできます。void *が型情報を失ったため、別の場所に保存する必要があります。
Dan Olson、

1
はい。別の型にキャストする場合はいつでも逆参照できますが、キャストしない場合は同じようにすることはできません。つまり、コンパイラは、キャストするまで数学演算に使用するアセンブリ命令を認識しません。
Zachary Hamm、

20
GCCはvoid *ポインターの算術をと同じようchar *に扱うと思いますが、これは標準ではないので、それに依存すべきではありません。
ephemient

5
私がフォローしているのかわかりません。上記のOPの例では、入力タイプは関数のユーザーによって正しいことが保証されています。既知の型の値をポインターに割り当てるものがある場合、それをvoidポインターにキャストしてから、整列されなくなる可能性がある元のポインター型にキャストし直しますか?なぜそれらをvoidポインターにキャストしたからといって、コンパイラーがランダムに整列を解除する理由を理解できない理由はわかりません。それとも、ユーザーが関数に渡した引数の型を知っていることをユーザーが信頼できないと単純に言っているのですか?
2014年

2
@doliver#2を意味しました(「ユーザーが関数に渡した引数の型を知っているとは信じられません」)。しかし、6年前から私の答えを再訪(ぐふ、それは本当に長いということであったのか?)私はあなたが何を意味するか見ることができ、それはありません常にケース:オリジナルのポインタは適切な型に指摘したときに、それはすでに整列されるだろう、それはそうアライメントの問題がなくても安全にキャストできます。
Alex B

31

Cではvoid *、明示的なキャストを行わなくても、aを別の型のオブジェクトへのポインターに変換できます。

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

ただし、これはより一般的な方法で関数を記述するのには役立ちません。

void *ポインターを逆参照するとポイントされたオブジェクトの値が取得されるため、別のポインター型に変換してを逆参照することはできません。ネイキッドvoidは有効なタイプではないため、aを参照解除することvoid *はできません。

ポインター演算とはsizeof、ポイントされたオブジェクトの倍数でポインター値を変更することです。繰り返しますが、voidは真の型でsizeof(void)はないため、意味がないため、ではポインタ演算は無効ですvoid *。(一部の実装では、と同等のポインター演算を使用してそれを許可していますchar *。)


1
@CharlesBaileyコードでは、を記述しvoid abc(void *a, int b)ます。しかし、なぜ使用しないのですか?void abc(int *a, int b)あなたの関数では最終的にを定義するint *test = a;ので、それは1つの変数を保存し、別の変数を定義することによる他の利点はありません。このようにvoid *パラメーターとして使用し、関数の後半で変数をキャストすることで、多くのコードが記述されています。しかし、この関数は作者によって記述されているため、変数の使用法を知っている必要がありますvoid *。おかげで
ライオンライ

15

JavaやC#とは異なり、Cでは、void*ポインターが指すオブジェクトのタイプを正常に「推測」する可能性はまったくないことに注意してください。getClass()この情報はどこにも見つからないため、単にに似たものは存在しません。そのため、探している「ジェネリック」の種類にint bは、例のやprintf関数ファミリのフォーマット文字列などの明示的なメタ情報が常に付属しています。


7

voidポインターは汎用ポインターと呼ばれ、任意のデータ型の変数を参照できます。


6

これまでのところ、voidポインタに関する過小評価は次のとおりです。

ポインタ変数がキーワードvoidを使用して宣言されると、汎用ポインタ変数になります。任意のデータ型(char、int、floatなど)の任意の変数のアドレスをvoidポインター変数に割り当てることができます。

main()
{
    int *p;

    void *vp;

    vp=p;
} 

他のデータ型ポインターはvoidポインターに割り当てることができるため、absolut_value(以下に示すコード)関数で使用しました。一般的な機能を作るため。

整数または浮動小数点を引数として取り、負の場合は+ veにする単純なCコードを記述しようとしました。私は次のコードを書きました、

#include<stdio.h>

void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
{
    if ( *j < 0 )
        *j = *j * (-1);

}

int main()
{
    int i = 40;
    float f = -40;
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    absolute_value(&i);
    absolute_value(&f);
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    return 0;
}   

しかし、エラーが発生していたので、私はvoidポインターでの私の理解が正しくないことを知るようになりました:(。

voidポインターについてさらに理解する必要があるのはそれです。

逆参照するには、voidポインター変数を型キャストする必要があります。これは、voidポインターに関連付けられたデータ型がないためです。コンパイラーがvoidポインターによって指し示されているデータのタイプを知る(または推測する)方法はありません。そのため、voidポインターが指すデータを取得するために、voidポインターの場所に保持されているデータの正しい型を使用してデータを型キャストします。

void main()

{

    int a=10;

    float b=35.75;

    void *ptr; // Declaring a void pointer

    ptr=&a; // Assigning address of integer to void pointer.

    printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.

    ptr=&b; // Assigning address of float to void pointer.

    printf("The value of float variable is= %f",*( (float*) ptr) );

}

プログラマがエンドユーザーが入力したデータのデータ型がわからない場合、voidポインタは非常に役立ちます。このような場合、プログラマーはvoidポインターを使用して、不明なデータ型の場所を指すことができます。データの種類をユーザに通知するようにプログラムを設定することができ、ユーザが入力した情報に従ってタイプキャストを実行することができる。コードスニペットを以下に示します。

void funct(void *a, int z)
{
    if(z==1)
    printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
    else if(z==2)
    printf("%c",*(char*)a); // Typecasting for character pointer.
    else if(z==3)
    printf("%f",*(float*)a); // Typecasting for float pointer
}

voidポインターについて留意すべきもう1つの重要な点は、voidポインターではポインター演算を実行できないことです。

void *ptr;

int a;

ptr=&a;

ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.

だから今、私は自分の間違いが何であるかを理解しました。同じことを修正しています。

参照:

http://www.antoarts.com/void-pointers-in-c/

http://www.circuitstoday.com/void-pointers-in-c

新しいコードは次のとおりです。


#include<stdio.h>
#define INT 1
#define FLOAT 2

void absolute_value ( void *j, int *n)
{
    if ( *n == INT) {
        if ( *((int*)j) < 0 )
            *((int*)j) = *((int*)j) * (-1);
    }
    if ( *n == FLOAT ) {
        if ( *((float*)j) < 0 )
            *((float*)j) = *((float*)j) * (-1);
    }
}


int main()
{
    int i = 0,n=0;
    float f = 0;
    printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
    scanf("%d",&n);
    printf("\n");
    if( n == 1) {
        scanf("%d",&i);
        printf("value entered before absolute function exec = %d \n",i);
        absolute_value(&i,&n);
        printf("value entered after absolute function exec = %d \n",i);
    }
    if( n == 2) {
        scanf("%f",&f);
        printf("value entered before absolute function exec = %f \n",f);
        absolute_value(&f,&n);
        printf("value entered after absolute function exec = %f \n",f);
    }
    else
    printf("unknown entry try again\n");
    return 0;
}   

ありがとうございました、


2

いいえ、できません。間接参照された値はどのタイプを持つべきですか?


2
void abc(void *a, int b) {
  char *format[] = {"%d", "%c", "%f"};
  printf(format[b-1], a);
}

このプログラムは可能ですか

2
はい、これは可能です(ただし、コードはbの範囲チェックなしでは危険です)。Printfは可変数の引数を取り、aは任意のポインター(タイプ情報なし)としてスタックにプッシュされ、フォーマット文字列からの情報を使用してva_argマクロを使用してprintf関数内にポップされます。
hlovdal

@SiegeX:移植できないものと未定義の可能性があるものについて説明してください。
Gauthier

@Gauthierがフォーマット指定子を含む変数を渡すことは、移植性がなく、潜在的に未定義の動作です。そのようなことをしたい場合は、の「vs」フレーバーを見てくださいprintfこの答えはその良い例です。
SiegeX

実際には、おそらくint b列挙型に置き換えますが、その列挙型に後で変更を加えると、この関数が機能しなくなります。
Jack Stout、

1

ifsを使用せずに、この関数をジェネリックにしたいと思います。出来ますか?

私が見る唯一の簡単な方法は、Cプログラミング言語のAFAIKでは使用できないオーバーロードを使用することです。

あなたのプログラムのC ++プログラミング言語を検討しましたか?または、その使用を禁止する制約はありますか?


わかりました、それは残念です。私の観点からは、私には解決策はありません。実際には、printf()およびcoutと同じです。印刷を実装する2つの異なる方法。printf()はif()ステートメントを使用してフォーマット文字列をデコードします(私が推測するのと同じようなものです)。一方、coutの場合、演算子<<はオーバーロードされた関数です
yves Baumes

1

ポインタに関する簡単なポインタを次に示します。httpsvoid//www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 — Voidポインター

voidポインターは、それが指しているオブジェクトのタイプを認識しないため、直接逆参照することはできません!むしろ、ボイドポインターは、逆参照される前に、最初に明示的に別のポインタータイプにキャストする必要があります。

voidポインターが何を指しているのかわからない場合、キャスト先を知るにはどうすればよいですか?最終的に、それを追跡するのはあなた次第です。

無効なポインター

voidポインターでポインター演算を行うことはできません。これは、ポインタ演算では、ポインタが指しているオブジェクトのサイズを知る必要があるため、ポインタを適切に増分または減分できるためです。

マシンのメモリがバイトアドレス指定可能であり、アラインされたアクセスを必要としない場合、aを解釈する最も一般的でアトミックな(マシンレベルの表現に最も近い)方法はvoid*、バイトへのポインター、uint8_t*です。a void*をaにキャストすると、uint8_t*たとえば、そのアドレスから始まる最初の1/2/4/8 / however-many-you-desireバイトを出力できますが、それ以外のことはできません。

uint8_t* byte_p = (uint8_t*)p;
for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
  printf("%x ",*i);
}

0

空のプリンターを簡単に印刷できます

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));

1
voidと同じサイズであることを誰が保証しintますか?
Ant_222 2016年

これは技術的にはvoidポインタを出力するのではなくint、ポインタが含むメモリアドレス(この場合はの値p)を出力します。
Marionumber1

0

Cは静的に型付けされた強く型付けされた言語であるため、コンパイルする前に変数の型を決定する必要があります。Cでジェネリックスをエミュレートしようとすると、C ++を再度書き換えようとするので、代わりにC ++を使用する方が良いでしょう。


0

voidポインターは汎用ポインターです。任意の変数の任意のデータ型のアドレスをvoidポインターに割り当てることができます。

int a = 10;
float b = 3.14;
void *ptr;
ptr = &a;
printf( "data is %d " , *((int *)ptr)); 
//(int *)ptr used for typecasting dereferencing as int
ptr = &b;
printf( "data is %f " , *((float *)ptr));
//(float *)ptr used for typecasting dereferencing as float

0

異なるデータ型はメモリ内で異なるサイズを持つため、その型を指定せずにポインターを逆参照することはできません。つまり、intは4バイト、charは1バイトです。


-1

基本的に、Cでは、「タイプ」はメモリ内のバイトを解釈する方法です。たとえば、次のコードは

struct Point {
  int x;
  int y;
};

int main() {
  struct Point p;
  p.x = 0;
  p.y = 0;
}

「メインを実行するときに、4(整数のサイズ)+ 4(整数のサイズ)= 8(合計バイト)のメモリを割り当てたいと思います。タイプラベルの値にlvalueとして '.x'を書き込むと、コンパイル時にポイントし、ポインターのメモリロケーションと4バイトからデータを取得します。戻り値にコンパイル時のラベル "int"を指定します。

実行時のコンピュータ内部では、「ポイント」構造は次のようになります。

00000000 00000000 00000000 00000000 00000000 00000000 00000000

そして、あなたのvoid*データタイプは次のようになります:(32ビットコンピュータを想定)

10001010 11111001 00010010 11000101

これは質問の答えにはなりません
MM

-2

これは機能しませんが、void *は、関数への汎用ポインターを定義し、それを別の関数への引数として渡す(Javaのコールバックと同様)か、oopと同様の構造を定義するのに役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.