Concetto di puntatore vuoto nella programmazione C.


129

È possibile dereferenziare un puntatore vuoto senza il cast del tipo nel linguaggio di programmazione C?

Inoltre, c'è un modo per generalizzare una funzione che può ricevere un puntatore e memorizzarlo in un puntatore vuoto e usando quel puntatore vuoto, possiamo fare una funzione generalizzata?

per esempio:

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
}

Voglio rendere generica questa funzione senza usare le istruzioni if-else - è possibile?

Inoltre, se ci sono buoni articoli su Internet che spiegano il concetto di un puntatore vuoto, sarebbe utile fornire gli URL.

Inoltre, è possibile l'aritmetica dei puntatori con puntatori vuoti?

Risposte:


97

È possibile dereferenziare il puntatore vuoto senza eseguire il cast di tipo nel linguaggio di programmazione C ...

No, voidindica l'assenza di tipo, non è qualcosa a cui è possibile dedurre o assegnare.

c'è un modo per generalizzare una funzione che può ricevere il puntatore e memorizzarlo nel puntatore vuoto e usando quel puntatore vuoto possiamo fare una funzione generalizzata.

Non puoi semplicemente dereferenziarlo in modo portatile, poiché potrebbe non essere allineato correttamente. Potrebbe trattarsi di un problema su alcune architetture come ARM, in cui il puntatore a un tipo di dati deve essere allineato al limite della dimensione del tipo di dati (ad esempio, il puntatore all'intero a 32 bit deve essere allineato al limite di 4 byte per essere differenziato).

Ad esempio, leggendo uint16_tda 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);

Inoltre, è possibile l'aritmetica dei puntatori con puntatori vuoti ...

L'aritmetica del puntatore non è possibile sui puntatori a voidcausa della mancanza di valore concreto sotto il puntatore e quindi delle dimensioni.

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

2
A meno che non mi ricordi in modo errato, puoi tranquillamente dereferenziare un vuoto * in due modi. Il cast su char * è sempre accettabile, e se conosci il tipo originale a cui punta puoi lanciare su quel tipo. Il vuoto * ha perso le informazioni sul tipo, quindi dovrebbe essere archiviato altrove.
Dan Olson,

1
Sì, puoi sempre dereference se lanci in un altro tipo, ma non puoi fare lo stesso se non esegui il cast. In breve, il compilatore non saprà quali istruzioni di assemblaggio usare per le operazioni matematiche fino a quando non lo lanci.
Zachary Hamm,

20
Penso che GCC tratti l'aritmetica sui void *puntatori allo stesso modo char *, ma non è standard e non dovresti fare affidamento su di esso.
effimero

5
Non sono sicuro di seguirlo. Ad esempio, l'OP elencato sopra i tipi in entrata sono garantiti come corretti dall'utente della funzione. Stai dicendo che se abbiamo qualcosa in cui assegniamo un valore di tipo noto a un puntatore, esegui il cast su un puntatore vuoto e poi lo riconduce al tipo di puntatore originale che potrebbe non essere allineato? Non capisco perché il compilatore ti minerebbe in quel modo solo cose che si allineano casualmente semplicemente perché le hai lanciate in un puntatore vuoto. O stai semplicemente dicendo che non possiamo fidarci che l'utente conosca il tipo di argomenti che ha passato alla funzione?
doliver

2
@doliver intendevo # 2 ("non possiamo fidarci che l'utente conosca il tipo di argomenti passati alla funzione"). Ma rivisitando la mia risposta di 6 anni fa (eh, è ​​davvero passato così tanto tempo?) Capisco cosa intendi, non è sempre il caso: quando il puntatore originale puntava al tipo corretto, sarebbe già stato allineato, quindi sarebbe sicuro lanciare senza problemi di allineamento.
Alex B,

31

In C, a void *può essere convertito in un puntatore a un oggetto di un tipo diverso senza un cast esplicito:

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

Questo non aiuta a scrivere la tua funzione in un modo più generico, però.

Non è possibile dereferenziare void *a convertendolo in un tipo di puntatore diverso in quanto il dereferenziamento di un puntatore sta ottenendo il valore dell'oggetto puntato. Un nudo voidnon è un tipo valido, quindi void *non è possibile distinguere a .

L'aritmetica del puntatore riguarda la modifica dei valori del puntatore per multipli degli sizeofoggetti puntati. Ancora una volta, poiché voidnon è un tipo vero, sizeof(void)non ha alcun significato, quindi l'aritmetica del puntatore non è valida void *. (Alcune implementazioni lo consentono, usando l'aritmetica puntatore equivalente per char *.)


1
@CharlesBailey Nel tuo codice, scrivi void abc(void *a, int b). Ma perché non usarlo void abc(int *a, int b), dato che nella tua funzione alla fine definirai int *test = a;, salverebbe una variabile, e non vedo altri vantaggi definendo un'altra variabile. Vedo molti codici scritti in questo modo usando void *come parametri e lanciando variabili più avanti nella funzione. Ma poiché questa funzione è scritta dall'autore, quindi deve conoscere l'uso della variabile, quindi perché usarla void *? Grazie
Lion Lai,

15

Dovresti essere consapevole che in C, a differenza di Java o C #, non c'è assolutamente alcuna possibilità di "indovinare" con successo il tipo di oggetto a cui void*punta un puntatore. Qualcosa di simile al getClass()semplice non esiste, dal momento che questa informazione non si trova da nessuna parte. Per questo motivo, il tipo di "generico" che stai cercando arriva sempre con una metainformazione esplicita, come l' int besempio o la stringa di formato nella printffamiglia di funzioni.


7

Un puntatore vuoto è noto come puntatore generico, che può fare riferimento a variabili di qualsiasi tipo di dati.


6

Finora la mia minimizzazione sul puntatore vuoto è la seguente.

Quando una variabile di puntatore viene dichiarata utilizzando la parola chiave void, diventa una variabile di puntatore per uso generico. L'indirizzo di qualsiasi variabile di qualsiasi tipo di dati (char, int, float ecc.) Può essere assegnato a una variabile puntatore vuoto.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Poiché è possibile assegnare un altro puntatore di tipo di dati al puntatore vuoto, è stato utilizzato nella funzione absolut_value (codice mostrato di seguito). Per fare una funzione generale.

Ho provato a scrivere un semplice codice C che prende intero o float come argomento e cerca di renderlo + ve, se negativo. Ho scritto il seguente codice,

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

Ma stavo ricevendo un errore, quindi sono venuto a sapere che la mia comprensione con il puntatore vuoto non è corretta :(. Quindi ora mi sposterò verso la raccolta di punti, perché è così.

Le cose che ho bisogno di capire di più sui puntatori del vuoto sono queste.

Per dereferenziarlo è necessario eseguire il cast della variabile puntatore vuoto. Questo perché un puntatore vuoto non ha alcun tipo di dati associato ad esso. Non è possibile che il compilatore possa sapere (o indovinare?) A quale tipo di dati viene indicato dal puntatore vuoto. Quindi, per prendere i dati a cui punta un puntatore vuoto, li digitiamo con il tipo corretto di dati conservati nella posizione dei puntatori vuoto.

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 puntatore vuoto può essere davvero utile se il programmatore non è sicuro del tipo di dati immesso dall'utente finale. In tal caso, il programmatore può utilizzare un puntatore vuoto per puntare alla posizione del tipo di dati sconosciuto. Il programma può essere impostato in modo tale da chiedere all'utente di informare il tipo di dati e il tipo di casting può essere eseguito in base alle informazioni immesse dall'utente. Di seguito è riportato un frammento di codice.

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 altro punto importante da tenere presente riguardo ai puntatori vuoti è che - l'aritmetica del puntatore non può essere eseguita in un puntatore vuoto.

void *ptr;

int a;

ptr=&a;

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

Quindi ora ho capito qual era il mio errore. Sto correggendo lo stesso.

Riferimenti :

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

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

Il nuovo codice è come mostrato di seguito.


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

Grazie,


2

No, non è possibile. Che tipo dovrebbe avere il valore con riferimento?


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

Questo programma è possibile ... Verificare, se possibile, nella programmazione C ...
AGeek

2
Sì, questo è possibile (sebbene il codice sia pericoloso senza un controllo di intervallo per b). Printf accetta un numero variabile di argomenti e a viene semplicemente inserito nello stack come qualsiasi puntatore (senza alcuna informazione sul tipo) e visualizzato all'interno della funzione printf usando macro va_arg usando le informazioni dalla stringa di formato.
hlovdal,

@SiegeX: descrivi ciò che non è portatile e ciò che potrebbe non essere definito.
Gauthier,

@Gauthier che passa una variabile che contiene identificatori di formato non è un comportamento portatile e potenzialmente non definito. Se vuoi fare qualcosa del genere, guarda il sapore "vs" di printf. Questa risposta ne è un buon esempio.
SiegeX,

In pratica, probabilmente sostituirò int bun enum, ma apportare qualsiasi modifica successiva a quell'enum interromperebbe questa funzione.
Jack Stout,

1

Voglio rendere questa funzione generica, senza usare ifs; È possibile?

L'unico modo semplice che vedo è usare il sovraccarico .. che non è disponibile nella lingua di programmazione C AFAIK.

Hai considerato la lingua di programmazione C ++ per il tuo programma? O c'è qualche vincolo che ne proibisce l'uso?


Ok, è un peccato. Dal mio punto di vista quindi non vedo soluzioni. In realtà, è lo stesso di printf () & cout: 2 modi diversi per implementare la stampa. printf () usa l'istruzione if () per decodificare la stringa di formato (suppongo o qualcosa di simile), mentre per l'operatore caso cout << è una funzione sovraccarica
yves Baumes

1

Ecco un breve puntatore sui voidpuntatori: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Puntatori del vuoto

Poiché il puntatore vuoto non conosce il tipo di oggetto a cui punta, non può essere verificato direttamente! Piuttosto, il puntatore vuoto deve prima essere esplicitamente proiettato su un altro tipo di puntatore prima di essere negato.

Se un puntatore vuoto non sa a cosa sta puntando, come sappiamo a cosa lanciarlo? In definitiva, spetta a te tenerne traccia.

Miscellanea del puntatore del vuoto

Non è possibile eseguire l'aritmetica del puntatore su un puntatore vuoto. Questo perché l'aritmetica del puntatore richiede al puntatore di sapere a quale oggetto di dimensione sta puntando, quindi può incrementare o decrementare il puntatore in modo appropriato.

Supponendo memoria della macchina è byte indirizzabile e non richiede accessi allineate, il modo più generico e atomico (più vicino alla rappresentazione livello macchina) di interpretare un void*è un puntatore a un byte, uint8_t*. Lanciare a in void*a uint8_t*ti consentirebbe, ad esempio, di stampare i primi 1/2/4/8 / comunque-quanti-desideri-byte a partire da quell'indirizzo, ma non puoi fare molto altro.

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

0

È possibile stampare facilmente una stampante vuota

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

1
Chi garantisce che voidabbia le stesse dimensioni di int?
Ant_222

Questo non sta tecnicamente stampando un voidpuntatore, ma sta stampando intall'indirizzo di memoria che il puntatore contiene (che in questo caso sarebbe il valore di p).
Marionumber1,

0

Poiché C è un linguaggio tipizzato staticamente, fortemente tipizzato, è necessario decidere il tipo di variabile prima della compilazione. Quando provi a emulare i generici in C, finirai per provare a riscrivere nuovamente C ++, quindi sarebbe meglio usare C ++ invece.


0

Il puntatore vuoto è un puntatore generico. L'indirizzo di qualsiasi tipo di dati di qualsiasi variabile può essere assegnato a un puntatore vuoto.

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

Non è possibile sottovalutare un puntatore senza specificarne il tipo poiché tipi di dati diversi avranno dimensioni diverse in memoria, ovvero un int di 4 byte, un carattere di 1 byte.


-1

Fondamentalmente, in C, i "tipi" sono un modo per interpretare i byte in memoria. Ad esempio, quale sia il seguente codice

struct Point {
  int x;
  int y;
};

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

Dice "Quando eseguo main, desidero allocare 4 (dimensione di numero intero) + 4 (dimensione di numero intero) = 8 (byte totali) di memoria. Quando scrivo '.x' come un valore su un valore con l'etichetta del tipo Punta al momento della compilazione, recupera i dati dalla posizione di memoria del puntatore più quattro byte. Assegna al valore restituito l'etichetta del tempo di compilazione "int". "

All'interno del computer in fase di esecuzione, la struttura "Punto" è simile alla seguente:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Ed ecco come void*potrebbe apparire il tuo tipo di dati: (supponendo un computer a 32 bit)

10001010 11111001 00010010 11000101

Questo non risponde alla domanda
MM

-2

Questo non funzionerà, tuttavia void * può aiutare molto nella definizione del puntatore generico alle funzioni e nel passarlo come argomento a un'altra funzione (simile al callback in Java) o definirlo una struttura simile a oop.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.