Concept de pointeur vide en programmation C


129

Est-il possible de déréférencer un pointeur void sans conversion de type dans le langage de programmation C?

En outre, y a-t-il un moyen de généraliser une fonction qui peut recevoir un pointeur et le stocker dans un pointeur void et en utilisant ce pointeur void, pouvons-nous créer une fonction généralisée?

par exemple:

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
}

Je veux rendre cette fonction générique sans utiliser les instructions if-else - est-ce possible?

De plus, s'il existe de bons articles sur Internet qui expliquent le concept de pointeur vide, il serait avantageux que vous puissiez fournir les URL.

De plus, l'arithmétique des pointeurs avec des pointeurs vides est-elle possible?

Réponses:


97

Est-il possible de déréférencer le pointeur void sans conversion de type en langage de programmation C ...

Non, voidindique l'absence de type, ce n'est pas quelque chose que vous pouvez déréférencer ou affecter.

existe-t-il un moyen de généraliser une fonction qui peut recevoir un pointeur et le stocker dans un pointeur void et en utilisant ce pointeur vide, nous pouvons créer une fonction généralisée ...

Vous ne pouvez pas simplement le déréférencer de manière portable, car il peut ne pas être correctement aligné. Cela peut être un problème sur certaines architectures comme ARM, où le pointeur vers un type de données doit être aligné à la limite de la taille du type de données (par exemple, le pointeur vers un entier 32 bits doit être aligné sur une limite de 4 octets pour être déréférencé).

Par exemple, lire à uint16_tpartir de 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);

L'arithmétique des pointeurs avec des pointeurs vides est-elle également possible ...

L'arithmétique des pointeurs n'est pas possible sur les pointeurs de en voidraison du manque de valeur concrète sous le pointeur et donc de la taille.

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

2
Sauf si je me souviens mal, vous pouvez en toute sécurité déréférencer un vide * de deux manières. La conversion en caractère * est toujours acceptable, et si vous connaissez le type d'origine vers lequel il pointe, vous pouvez effectuer une conversion vers ce type. Le void * a perdu les informations de type, il devrait donc être stocké ailleurs.
Dan Olson

1
Oui, vous pouvez toujours déréférencer si vous lancez dans un autre type, mais vous ne pouvez pas faire de même si vous ne lancez pas . En bref, le compilateur ne saura pas quelles instructions d'assemblage utiliser pour les opérations mathématiques tant que vous ne les castez pas.
Zachary Hamm

20
Je pense que GCC traite l'arithmétique sur les void *pointeurs de la même manière que char *, mais ce n'est pas standard et vous ne devriez pas vous y fier.
éphémère

5
Je ne suis pas sûr de suivre. Dans l'exemple de l'OP énuméré ci-dessus, les types entrants sont garantis corrects par l'utilisateur de la fonction. Êtes-vous en train de dire que si nous avons quelque chose où nous attribuons une valeur de type connu à un pointeur, le convertissons en un pointeur void, puis le renvoyons au type de pointeur d'origine pour qu'il devienne non aligné? Je ne comprends pas pourquoi le compilateur vous affaiblirait comme ça en désalignant simplement les choses au hasard simplement parce que vous les convertissez en un pointeur vide. Ou dites-vous simplement que nous ne pouvons pas faire confiance à l'utilisateur pour connaître le type d'arguments qu'il a transmis à la fonction?
doliver

2
@doliver Je voulais dire # 2 ("nous ne pouvons pas faire confiance à l'utilisateur pour connaître le type d'arguments qu'il a passé à la fonction"). Mais revisitant ma réponse d'il y a 6 ans (euh, ça fait vraiment si longtemps?) Je peux voir ce que tu veux dire, ce n'est pas toujours le cas: quand le pointeur d'origine pointait vers le bon type, il serait déjà aligné, donc ça Je serais sûr de lancer sans problèmes d'alignement.
Alex B

31

En C, a void *peut être converti en un pointeur vers un objet d'un type différent sans conversion explicite:

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

Cela n'aide cependant pas à écrire votre fonction de manière plus générique.

Vous ne pouvez pas déréférencer un void *en le convertissant en un autre type de pointeur, car le déréférencement d'un pointeur obtient la valeur de l'objet pointé. Un nu voidn'est pas un type valide, il void *n'est donc pas possible de déréférencer un .

L'arithmétique des pointeurs consiste à modifier les valeurs des pointeurs par des multiples des sizeofobjets pointés. Encore une fois, parce que ce voidn'est pas un type vrai, sizeof(void)n'a pas de signification, l'arithmétique du pointeur n'est donc pas valide sur void *. (Certaines implémentations le permettent, en utilisant l'arithmétique du pointeur équivalent pour char *.)


1
@CharlesBailey Dans votre code, vous écrivez void abc(void *a, int b). Mais pourquoi ne pas l'utiliser void abc(int *a, int b), car dans votre fonction que vous définirez éventuellement int *test = a;, cela sauverait une variable, et je ne vois pas d'autres avantages à définir une autre variable. Je vois de nombreux codes écrits de cette manière en utilisant void *comme paramètres et en transposant des variables plus tard dans la fonction. Mais puisque cette fonction est écrite par l'auteur, il doit donc connaître l'utilisation de la variable, pourquoi l'utiliser void *? Merci
Lion Lai

15

Vous devez être conscient qu'en C, contrairement à Java ou C #, il n'y a absolument aucune possibilité de "deviner" avec succès le type d'objet vers void*lequel pointe un pointeur. Quelque chose de similaire à getClass()n'existe tout simplement pas, car cette information est introuvable. Pour cette raison, le type de "générique" que vous recherchez est toujours accompagné de méta-informations explicites, comme int bdans votre exemple ou la chaîne de format dans la printffamille de fonctions.


7

Un pointeur void est appelé pointeur générique, qui peut faire référence à des variables de n'importe quel type de données.


6

Jusqu'à présent, ma compréhension du pointeur de vide est la suivante.

Lorsqu'une variable de pointeur est déclarée à l'aide du mot clé void, elle devient une variable de pointeur à usage général. L'adresse de toute variable de tout type de données (char, int, float etc.) peut être affectée à une variable de pointeur void.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Comme un autre pointeur de type de données peut être affecté au pointeur void, je l'ai donc utilisé dans la fonction absolut_value (code ci-dessous). Pour faire une fonction générale.

J'ai essayé d'écrire un code C simple qui prend un entier ou un flottant comme argument et essaie de le rendre + ve, s'il est négatif. J'ai écrit le code suivant,

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

Mais j'obtenais une erreur, alors j'ai appris que ma compréhension du pointeur de vide n'était pas correcte :(. Alors maintenant, je vais passer à collecter des points pourquoi.

Ce que j'ai besoin de mieux comprendre sur les pointeurs vides, c'est que.

Nous devons transtyper la variable de pointeur void pour la déréférencer. Cela est dû au fait qu'un pointeur void n'a aucun type de données associé. Il n'y a aucun moyen pour le compilateur de savoir (ou de deviner?) Quel type de données est pointé par le pointeur void. Donc, pour prendre les données pointées par un pointeur void, nous les avons typées avec le type correct des données contenues dans l'emplacement des pointeurs vides.

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

}

Un pointeur void peut être vraiment utile si le programmeur n'est pas sûr du type de données des données entrées par l'utilisateur final. Dans un tel cas, le programmeur peut utiliser un pointeur vide pour pointer vers l'emplacement du type de données inconnu. Le programme peut être réglé de manière à demander à l'utilisateur d'informer le type de données et la diffusion de type peut être effectuée en fonction des informations entrées par l'utilisateur. Un extrait de code est donné ci-dessous.

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
}

Un autre point important que vous devez garder à l'esprit à propos des pointeurs vides est que - l'arithmétique des pointeurs ne peut pas être exécutée dans un pointeur vide.

void *ptr;

int a;

ptr=&a;

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

Alors maintenant, j'ai compris quelle était mon erreur. Je corrige la même chose.

Références :

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

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

Le nouveau code est comme indiqué ci-dessous.


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

Merci,


2

Non ce n'est pas possible. Quel type doit avoir la valeur déréférencée?


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

Ce programme est-il possible .... Veuillez vérifier si cela est possible en programmation C ...
AGeek

2
Oui, cela est possible (bien que le code soit dangereux sans un contrôle de portée pour b). Printf prend un nombre variable d'arguments et a est simplement poussé sur la pile comme n'importe quel pointeur (sans aucune information de type) et apparaît à l'intérieur de la fonction printf en utilisant les macros va_arg en utilisant les informations de la chaîne de format.
hlovdal

@SiegeX: veuillez décrire ce qui n'est pas portable et ce qui peut ne pas être défini.
Gauthier

@Gauthier passant une variable qui contient des spécificateurs de format n'est pas un comportement portable et potentiellement indéfini. Si vous voulez faire quelque chose comme ça, regardez la saveur «vs» de printf. Cette réponse a un bon exemple pour cela.
SiegeX

En pratique, je remplacerais probablement int bpar une énumération, mais toute modification ultérieure de cette énumération interromprait cette fonction.
Jack Stout

1

Je veux rendre cette fonction générique, sans utiliser ifs; c'est possible?

La seule façon simple que je vois est d'utiliser la surcharge .. qui n'est pas disponible dans le langage de programmation C AFAIK.

Avez-vous pensé au langage de programmation C ++ pour votre programme? Ou y a-t-il une contrainte qui interdit son utilisation?


Ok, c'est dommage. De mon point de vue, je ne vois donc aucune solution. En fait, c'est la même chose que printf () & cout: 2 façons différentes d'implémenter l'impression. printf () utilise l'instruction if () pour décoder la chaîne de format (je suppose ou quelque chose de similaire), tandis que pour l'opérateur de cas cout << est une fonction surchargée
yves Baumes

1

Voici un bref pointeur sur les voidpointeurs: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Pointeurs vides

Comme le pointeur void ne sait pas sur quel type d'objet il pointe, il ne peut pas être directement déréférencé! Au contraire, le pointeur void doit d'abord être converti explicitement en un autre type de pointeur avant d'être déréférencé.

Si un pointeur de vide ne sait pas sur quoi il pointe, comment savoir vers quoi le lancer? En fin de compte, c'est à vous de faire le suivi.

Mélange de pointeurs de vide

Il n'est pas possible de faire de l'arithmétique de pointeur sur un pointeur vide. Cela est dû au fait que l'arithmétique du pointeur nécessite que le pointeur sache sur quelle taille l'objet il pointe, afin qu'il puisse incrémenter ou décrémenter le pointeur de manière appropriée.

En supposant que la mémoire de la machine est octet adressable et ne requiert pas accès alignés, la plus générique et atomique (plus proche de la représentation du niveau de la machine) manière d'interpréter un void*est un pointeur sur un octet, uint8_t*. Le cast de a void*en a uint8_t*vous permettrait, par exemple, d'imprimer les premiers 1/2/4/8 / autant que vous le désirez en commençant à cette adresse, mais vous ne pouvez pas faire grand chose d'autre.

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

0

Vous pouvez facilement imprimer une imprimante vide

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

1
Qui garantit qu'il voida la même taille que int?
Ant_222

Il ne s'agit pas techniquement d'imprimer un voidpointeur, c'est d'imprimer le intà l'adresse mémoire que le pointeur contient (qui dans ce cas serait la valeur de p).
Marionumber1

0

Comme C est un langage à typage statique et fortement typé, vous devez décider du type de variable avant la compilation. Lorsque vous essayez d'émuler des génériques en C, vous finirez par essayer de réécrire à nouveau C ++, il serait donc préférable d'utiliser C ++ à la place.


0

Le pointeur void est un pointeur générique. L'adresse de n'importe quel type de données de n'importe quelle variable peut être assignée à un pointeur 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

Vous ne pouvez pas déréférencer un pointeur sans spécifier son type car différents types de données auront des tailles différentes en mémoire, c'est-à-dire qu'un entier vaut 4 octets, un caractère vaut 1 octet.


-1

Fondamentalement, en C, les «types» sont un moyen d'interpréter les octets en mémoire. Par exemple, ce que le code suivant

struct Point {
  int x;
  int y;
};

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

Dit "Quand j'exécute main, je veux allouer 4 (taille de l'entier) + 4 (taille de l'entier) = 8 (octets totaux) de mémoire. Quand j'écris '.x' comme lvalue sur une valeur avec l'étiquette de type Pointez au moment de la compilation, récupérez les données à partir de l'emplacement mémoire du pointeur plus quatre octets. Donnez à la valeur de retour l'étiquette de compilation "int".

À l'intérieur de l'ordinateur au moment de l'exécution, votre structure "Point" ressemble à ceci:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Et voici à quoi void*pourrait ressembler votre type de données: (en supposant un ordinateur 32 bits)

10001010 11111001 00010010 11000101

Cela ne répond pas à la question
MM

-2

Cela ne fonctionnera pas, mais void * peut beaucoup aider à définir un pointeur générique vers des fonctions et à le transmettre en tant qu'argument à une autre fonction (similaire au callback en Java) ou à lui définir une structure similaire à oop.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.