Puntatore aritmetico per puntatore vuoto in C


177

Quando un puntatore ad un tipo particolare (per esempio int, char, float, ..) viene incrementato il suo valore viene incrementato della dimensione del tipo di dati. Se viene incrementato un voidpuntatore che punta a dati di dimensioni x, come si arriva a puntare xbyte in anticipo? Come fa il compilatore a sapere di aggiungere xal valore del puntatore?



3
La domanda suona come se supponesse che il compilatore (/ run-time) sappia a quale tipo di oggetto è stato impostato il puntatore e ne aggiunge le dimensioni al puntatore. Questo è un malinteso completo: conosce solo l'indirizzo.
PJTraill

2
"Se un voidpuntatore che punta a dati di dimensioni xviene incrementato, come arriva a puntare i xbyte in avanti?" Non Perché le persone che hanno tali domande non possono metterle alla prova prima di chiedere - sai, almeno al minimo, dove controllano se effettivamente si compila, cosa che non succede. -1, non posso credere che abbia +100 e -0.
underscore_d

Risposte:


287

Conclusione finale: l'aritmetica su a void*è illegale sia in C che in C ++.

GCC lo consente come estensione, vedi Arithmetic on void- e Function-Pointers (nota che questa sezione fa parte del capitolo "C Extensions" del manuale). Clang e ICC probabilmente consentono l' void*aritmetica ai fini della compatibilità con GCC. Altri compilatori (come MSVC) non consentono l'aritmetica void*e GCC non lo consente se -pedantic-errorsviene specificato il -Werror-pointer-arithflag o se viene specificato il flag (questo flag è utile se la propria base di codice deve essere compilata anche con MSVC).

Lo standard C parla

Le citazioni sono tratte dalla bozza n1256.

La descrizione dello standard dell'operazione di aggiunta afferma:

6.5.6-2: Inoltre, entrambi gli operandi devono avere un tipo aritmetico oppure un operando deve essere un puntatore a un tipo di oggetto e l'altro deve avere un tipo intero.

Quindi, la domanda qui è se void*è un puntatore a un "tipo di oggetto", o equivalentemente, se voidè un "tipo di oggetto". La definizione di "tipo di oggetto" è:

6.2.5.1: I tipi sono suddivisi in tipi di oggetti (tipi che descrivono completamente gli oggetti), tipi di funzione (tipi che descrivono le funzioni) e tipi incompleti (tipi che descrivono gli oggetti ma mancano delle informazioni necessarie per determinarne le dimensioni).

E lo standard definisce voidcome:

6.2.5-19: il voidtipo comprende un insieme di valori vuoto; è un tipo incompleto che non può essere completato.

Poiché voidè un tipo incompleto, non è un tipo di oggetto. Pertanto non è un operando valido per un'operazione di aggiunta.

Pertanto non è possibile eseguire l'aritmetica del puntatore su un voidpuntatore.

Appunti

Inizialmente, si pensava che l' void*aritmetica fosse consentita, a causa di queste sezioni dello standard C:

6.2.5-27: Un puntatore a vuoto deve avere gli stessi requisiti di rappresentazione e allineamento di un puntatore a un tipo di carattere.

Però,

Gli stessi requisiti di rappresentazione e allineamento implicano l'intercambiabilità degli argomenti alle funzioni, restituiscono valori dalle funzioni e membri dei sindacati.

Quindi questo significa che printf("%s", x)ha lo stesso significato se xha tipo char*o void*, ma non significa che puoi fare l'aritmetica su a void*.

Nota del redattore: questa risposta è stata modificata per riflettere la conclusione finale.


7
Dallo standard C99: (6.5.6.2) Inoltre, entrambi gli operandi devono avere un tipo aritmetico oppure un operando deve essere un puntatore a un tipo di oggetto e l'altro deve avere un tipo intero. (6.2.5.19) Il tipo vuoto comprende un insieme di valori vuoto; è un tipo incompleto che non può essere completato. Penso che ciò chiarisca che l' void*aritmetica del puntatore non è consentita. GCC ha un'estensione che consente di farlo.
Giobbe

1
se non ritieni più utile la tua risposta, puoi semplicemente eliminarla.
Caf

1
Questa risposta è stata utile anche se si è rivelata sbagliata in quanto contiene prove conclusive che i puntatori nulli non sono pensati per l'aritmetica.
Ben Flynn,

1
Questa è una buona risposta, ha la giusta conclusione e le citazioni necessarie, ma le persone che sono arrivate a questa domanda sono giunte a una conclusione sbagliata perché non hanno letto fino alla fine della risposta. L'ho modificato per renderlo più ovvio.
Dietrich Epp,

1
Clang e ICC non consentono l' void*aritmetica (almeno per impostazione predefinita).
Sergey Podobry,

61

L'aritmetica del puntatore non è consentita sui void*puntatori.


16
+1 L'aritmetica del puntatore è definita solo per i puntatori a tipi di oggetto (completi) . voidè un tipo incompleto che non può mai essere completato per definizione.
schot

1
@schot: esattamente. Inoltre, l'aritmetica del puntatore è definita solo su un puntatore a un elemento di un oggetto array e solo se il risultato dell'operazione sarebbe un puntatore a un elemento in quello stesso array o uno oltre l'ultimo elemento di quell'array. Se tali condizioni non sono soddisfatte, si tratta di un comportamento indefinito. (Dallo standard C99 6.5.6.8)
Lavoro

1
Apparentemente non è così con gcc 7.3.0. Il compilatore accetta p + 1024, dove p è nullo *. E il risultato è lo stesso di ((char *) p) + 1024
zzz777

@ zzz777 è un'estensione GCC, vedi il link nella risposta più votata.
Ruslan,

19

lanciarlo su un puntatore a carattere e incrementare il puntatore in avanti x byte avanti.


4
Perché preoccuparsi? Basta lanciarlo nel tipo giusto - che dovrebbe essere sempre noto - e incrementarlo di 1. Lanciare char, incrementare di xe quindi reinterpretare il nuovo valore in quanto un altro tipo è un comportamento inutile e indefinito.
underscore_d

9
Se stai scrivendo la tua funzione di ordinamento, che secondo man 3 qsortdovrebbe avere il void qsort(void *base, size_t nmemb, size_t size, [snip]), allora non hai modo di conoscere il "tipo giusto"
alisianoi,

16

Lo standard C non consente l' aritmetica del puntatore vuoto . Tuttavia, GNU C è consentito dal considerando le dimensioni dei vuoti IS 1.

Norma C11 §6.2.5

Paragrafo - 19

Il voidtipo comprende un insieme vuoto di valori; è un tipo di oggetto incompleto che non può essere completato.

Il seguente programma funziona correttamente nel compilatore GCC.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Possono essere altri compilatori generare un errore.



8

Devi lanciarlo su un altro tipo di puntatore prima di eseguire l'aritmetica del puntatore.


7

I puntatori Void possono puntare a qualsiasi blocco di memoria. Quindi il compilatore non sa quanti byte incrementare / decrementare quando proviamo l'aritmetica del puntatore su un puntatore vuoto. Pertanto i puntatori nulli devono essere prima convertiti in un tipo noto prima di poter essere coinvolti in qualsiasi aritmetica dei puntatori.

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.

-1

Il compilatore conosce per tipo cast. Dato un void *x:

  • x+1aggiunge un byte a x, il puntatore diventa bytex+1
  • (int*)x+1aggiunge sizeof(int)byte, il puntatore diventa bytex + sizeof(int)
  • (float*)x+1sizeof(float)byte di indirizzo , ecc.

Sebbene il primo elemento non sia portatile ed è contro il Galateo di C / C ++, è comunque corretto in C-language, il che significa che si compilerà su qualcosa sulla maggior parte dei compilatori che potrebbe richiedere un flag appropriato (come -Wpointer-arith)


2
Althought the first item is not portable and is against the Galateo of C/C++Vero. it is nevertheless C-language-correctFalso. Questo è un doppio pensiero! L'aritmetica del puntatore void *è sintatticamente illegale, non dovrebbe essere compilata e, se lo fa, produce un comportamento indefinito. Se un programmatore incurante può farlo compilare disabilitando alcuni avvisi, non è una scusa.
underscore_d

@underscore_d: Penso che alcuni compilatori lo permettessero come estensione, dal momento che è molto più conveniente che dover eseguire il cast per unsigned char*aggiungere un sizeofvalore a un puntatore.
supercat
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.