Perché il vuoto in C significa non vuoto?

24

Nei linguaggi strongmente tipizzati come Java e C #, void (o Void ) come un tipo di ritorno per un metodo sembra significare:

This method doesn't return anything. Nothing. No return. You will not receive anything from this method.

Ciò che è veramente strano è che in C, void come tipo di ritorno o anche come tipo di parametro metodo significa:

It could really be anything. You'd have to read the source code to find out. Good luck. If it's a pointer, you should really know what you're doing.

Considera i seguenti esempi in C:

void describe(void *thing)
{
    Object *obj = thing;
    printf("%s.\n", obj->description);
}

void *move(void *location, Direction direction)
{
    void *next = NULL;

    // logic!

    return next;
}

Ovviamente, il secondo metodo restituisce un puntatore, che per definizione potrebbe essere qualsiasi cosa.

Poiché C è più vecchio di Java e C #, perché questi linguaggi adottano void come "nulla" mentre C lo usa come "niente o niente (quando un puntatore)"?

    
posta Naftuli Kay 22.08.2014 - 22:33
fonte

11 risposte

56

La parola chiave void (non un puntatore) significa "nulla" in quelle lingue. Questo è coerente.

Come hai notato, void* significa "puntatore a qualsiasi cosa" nelle lingue che supportano i puntatori non elaborati (C e C ++). Questa è una decisione sfortunata perché come hai detto, rende void significa due cose diverse.

Non sono stato in grado di trovare la ragione storica alla base del riutilizzo di void per significare "niente" e "nulla" in contesti diversi, tuttavia C lo fa in molti altri punti. Ad esempio, static ha scopi diversi in diversi contesti. Esiste ovviamente un precedente nel linguaggio C per riutilizzare le parole chiave in questo modo, indipendentemente da ciò che si può pensare della pratica.

Java e C # sono abbastanza diversi da fare una pausa per correggere alcuni di questi problemi. Anche Java e C # "sicuri" non consentono puntatori grezzi e non hanno bisogno di una facile compatibilità con C (C # non non consente puntatori, ma la maggior parte del codice C # non rientra in questa categoria). Ciò consente loro di cambiare un po 'le cose senza preoccuparsi della retrocompatibilità. Un modo per farlo è introdurre una classe Object nella radice della gerarchia da cui ereditano tutte le classi, quindi un riferimento Object serve la stessa funzione di void* senza la cattiveria dei problemi di tipo e la gestione della memoria grezza.

    
risposta data 22.08.2014 - 23:06
fonte
32

void e void* sono due cose diverse. void in C indica esattamente la stessa cosa che fa in Java, un'assenza di un valore di ritorno. Un void* è un puntatore con un'assenza di un tipo.

Tutti i puntatori in C devono essere in grado di essere dereferenziati. Se hai cancellato un void* , che tipo ti aspetti di ottenere? Ricorda che i puntatori C non contengono alcuna informazione sul tipo runtime, quindi il tipo deve essere noto al momento della compilazione.

Dato questo contesto, l'unica cosa che puoi fare logicamente con un void* dereferenziato è ignorarlo, che è esattamente il comportamento che il tipo void denota.

    
risposta data 22.08.2014 - 23:46
fonte
19

Forse sarebbe più utile pensare a void come tipo di ritorno. Quindi il tuo secondo metodo leggerà "un metodo che restituisce un puntatore non tipizzato."

    
risposta data 22.08.2014 - 22:35
fonte
12

Aumentiamo un po 'la terminologia.

Dallo standard C 2011 online :

6.2.5 Types
...
19 The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.
...
6.3 Conversions
...
6.3.2.2 void

1 The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way, and implicit or explicit conversions (except to void) shall not be applied to such an expression. If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)

6.3.2.3 Pointers

1 A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

Un'espressione void non ha valore (sebbene possa avere effetti collaterali). Se ho una funzione definita per restituire void , in questo modo:

void foo( void ) { ... }

quindi la chiamata

foo();

non valuta un valore; Non posso assegnare il risultato a qualcosa, perché non c'è risultato.

Un puntatore a void è essenzialmente un tipo di puntatore "generico"; puoi assegnare un valore di void * a qualsiasi altro tipo di puntatore all'oggetto senza bisogno di un cast esplicito (motivo per cui tutti i programmatori C ti urleranno di lanciare il risultato di malloc ).

Non puoi dereferenziare direttamente un void * ; è necessario prima assegnarlo a un tipo di puntatore a oggetto diverso prima di poter accedere all'oggetto puntato.

void puntatori vengono utilizzati per implementare (più o meno) interfacce generiche; l'esempio canonico è la funzione di libreria qsort , che può ordinare array di qualsiasi tipo, come a patto che tu fornisca una funzione di confronto sensibile al tipo.

Sì, usare la stessa parola chiave per due concetti diversi (nessun valore rispetto a un puntatore generico) è confuso, ma non è come se non ci fosse un precedente; static ha più significati distinti sia in C che in C ++.

    
risposta data 23.08.2014 - 00:25
fonte
10

In Java and C#, void as a return type for a method seem to mean: this method doesn't return anything. Nothing. No return. You will not receive anything from this method.

Questa affermazione è corretta. È corretto anche per C e C ++.

in C, void as a return type or even as a method parameter type means something different.

Questa affermazione non è corretta. void come un tipo di ritorno in C o C ++ significa la stessa cosa che fa in C # e Java. Stai confondendo void con void* . Sono completamente diversi.

Isn't it confusing that void and void* mean two completely different things?

Sì.

What is a pointer? What is a void pointer?

Un puntatore è un valore che potrebbe essere dereferenziato . Il dereferenziamento di un puntatore valido fornisce una posizione di memoria del tipo puntato . Un puntatore void è un puntatore che non ha un particolare tipo puntato; deve essere convertito in un tipo di puntatore più specifico prima che il valore sia dereferenziato per produrre una posizione di memoria .

Are void pointers the same in C, C++, Java and C#?

Java non ha puntatori void; C #. Sono gli stessi di C e C ++ - un valore puntatore a cui non è associato alcun tipo specifico, che deve essere convertito in un tipo più specifico prima di dereferenziarlo per produrre un percorso di archiviazione.

Since C is older than Java and C#, why did these languages adopt void as meaning "nothing" while C used it as "nothing or anything (when a pointer)"?

La domanda è incoerente perché presuppone falsità. Facciamo alcune domande migliori.

Why did Java and C# adopt the convention that void is a valid return type, instead of, for instance, using the Visual Basic convention that functions are never void returning and subroutines are always void returning?

Per familiarizzare con i programmatori che arrivano a Java o C # dalle lingue in cui void è un tipo restituito.

Why did C# adopt the confusing convention that void* has a completely different meaning than void ?

Per familiarizzare con i programmatori che arrivano a C # dalle lingue in cui void* è un tipo di puntatore.

    
risposta data 23.08.2014 - 19:20
fonte
5
void describe(void *thing);
void *move(void *location, Direction direction);

La prima funzione non restituisce nulla. La seconda funzione restituisce un puntatore vuoto. Avrei dichiarato quella seconda funzione come

void* move(void* location, Direction direction);

Se può essere d'aiuto se si pensa a "vuoto" come a "senza significato". Il valore di ritorno di describe è "senza significato". Restituire un valore da tale funzione è illegale perché hai detto al compilatore che il valore restituito è privo di significato. Non è possibile acquisire il valore restituito da tale funzione perché è privo di significato. Non puoi dichiarare una variabile di tipo void perché è priva di significato. Ad esempio void nonsense; non ha senso ed è in realtà illegale. Nota anche che un array di% void void_array[42]; non ha senso.

Un puntatore a void ( void* ) è qualcosa di diverso. È un puntatore, non un array. Leggi void* come significato di un puntatore che punta a qualcosa "senza significato". Il puntatore è "senza significato", il che significa che non ha senso dereferenziare un tale puntatore. Cercare di farlo è in realtà illegale. Il codice che dereferenzia un puntatore vuoto non verrà compilato.

Quindi, se non puoi dereferenziare un puntatore vuoto e non puoi creare una serie di spazi vuoti, come puoi usarli? La risposta è che un puntatore a qualsiasi tipo può essere lanciato su e da void* . Lanciare un puntatore a void* e tornare a un puntatore al tipo originale produrrà un valore uguale al puntatore originale. Lo standard C garantisce questo comportamento. La ragione principale per cui si vedono così tanti puntatori void in C è che questa capacità fornisce un modo per implementare l'occultamento delle informazioni e la programmazione basata su oggetti in un linguaggio molto non orientato agli oggetti.

Infine, la ragione per cui spesso compare move come void *move(...) piuttosto che void* move(...) è perché mettere l'asterisco accanto al nome piuttosto che al tipo è una pratica molto comune in C. Il motivo è che il seguente la dichiarazione ti mette nei guai: int* iptr, jptr; Questo ti farebbe pensare che stai dichiarando due puntatori a un int . Non sei. Questa dichiarazione rende jptr an int anziché un puntatore a int . La dichiarazione corretta è int *iptr, *jptr; È possibile far finta che l'asterisco appartenga al tipo se si attiene alla pratica di dichiarare solo una variabile per dichiarazione. Ma tieni presente che stai fingendo.

    
risposta data 23.08.2014 - 02:22
fonte
2

In parole semplici, non c'è alcuna differenza . Lasciatemi fare alcuni esempi.

Dire che ho una classe chiamata Car.

Car obj = anotherCar; // obj is now of type Car
Car* obj = new Car(); // obj is now a pointer of type Car
void obj = 0; // obj has no type
void* = new Car(); // obj is a pointer with no type

Credo che qui la confusione si basi su come definiresti void vs void* . Un tipo di ritorno di void significa "Non restituisco nulla", mentre un tipo di ritorno di void* significa "Restituisco un puntatore di tipo nulla".

Questo è dovuto al fatto che un puntatore è un caso speciale. Un oggetto puntatore punterà sempre a una posizione in memoria, indipendentemente dal fatto che ci sia o meno un oggetto valido. Se il mio void* car fa riferimento a un byte, una parola, un'auto o una bicicletta non ha importanza perché il mio void* punta a qualcosa senza un tipo definito. Spetta a noi in seguito definirlo.

In linguaggi come C # e Java, la memoria viene gestita e il concetto di puntatori viene spostato nella classe Object. Invece di avere un riferimento a un oggetto di tipo "niente", sappiamo che perlomeno il nostro oggetto è di tipo "Oggetto". Lasciami spiegare perché.

Nel convenzionale C / C ++ se crei un oggetto ed elimini il suo puntatore, allora è impossibile accedere a quell'oggetto. Questo è ciò che è noto come perdita di memoria .

In C # / Java, tutto è un oggetto e questo consente al runtime di tenere traccia di ogni oggetto. Non ha necessariamente bisogno di sapere che il mio oggetto è un'Auto, ha semplicemente bisogno di conoscere alcune informazioni di base che sono già state prese in carico dalla classe Object.

Per noi programmatori, ciò vuol dire che gran parte delle informazioni che dovremmo occuparci manualmente di C / C ++ è gestita per noi dalla classe Object, eliminando la necessità del caso speciale che è il puntatore.

    
risposta data 23.08.2014 - 02:07
fonte
2

Cercherò di dire come si possa pensare a queste cose in C. La lingua ufficiale nello standard C non è davvero a proprio agio con void , e non cercherò di essere del tutto coerente con esso.

Il tipo void non è un cittadino di prima classe tra i tipi. Sebbene sia un tipo di oggetto, non può essere il tipo di qualsiasi oggetto (o valore), di un campo o di un parametro di funzione; tuttavia può essere il tipo di ritorno di una funzione, e quindi essere il tipo di un'espressione (fondamentalmente di chiamate di tali funzioni, ma le espressioni formate con l'operatore condizionale possono anche avere un tipo void). Ma anche l'uso minimo di void come un tipo di valore a cui lascia aperta la porta, ovvero che termina una funzione restituendo void con un'istruzione della forma return E; dove E è un'espressione di tipo void , è esplicitamente vietato in C (è consentito in C ++, ma per ragioni non applicabili a C).

Se gli oggetti di tipo void erano consentiti, avrebbero 0 bit (ovvero la quantità di informazioni nel valore di un'espressione di tipo void ); non sarebbe un grosso problema nel permettere tali oggetti, ma sarebbe piuttosto inutile (gli oggetti di dimensione 0 darebbero qualche difficoltà nel definire l'aritmetica del puntatore, che forse sarebbe meglio essere proibiti). L'insieme di valori diversi come un oggetto avrebbe un elemento (banale); il suo valore potrebbe essere preso, banalmente perché non ha alcuna sostanza, ma non può essere modificato (senza alcun valore diverso ). Pertanto, lo standard è confuso nel dire che void comprende un insieme vuoto di valori; un tipo di questo tipo potrebbe essere utile per descrivere espressioni che non possono essere valutate (come salti o chiamate non terminanti) sebbene il linguaggio C non utilizzi effettivamente tale tipo.

Non ammesso come tipo di valore, void è stato usato in almeno due modi non direttamente correlati per designare "proibito": specificare (void) come specifica di parametro nei tipi di funzione significa fornire qualsiasi argomento a loro è proibito (while ' () 'al contrario significa che tutto è permesso, e sì, anche fornire un'espressione void come argomento per le funzioni con (void) la specifica del parametro è proibito) e dichiarare void *p significa che l'espressione di dereferenziazione *p è vietato (ma non che sia un'espressione valida di tipo void ). Quindi hai ragione che questi usi di void non sono realmente coerenti con void come un tipo valido di espressioni. Tuttavia un oggetto di tipo void* non è realmente un puntatore a valori di qualsiasi tipo, significa un valore che viene considerato come un puntatore anche se non può essere dereferenziato e l'aritmetica del puntatore con essa non è consentita. Il "trattato come un puntatore" quindi in realtà significa solo che può essere lanciato da e verso qualsiasi tipo di puntatore senza perdita di informazioni. Tuttavia può anche essere usato per trasmettere valori interi da e verso, quindi non deve puntare a nulla.

    
risposta data 23.08.2014 - 08:47
fonte
0

In C # e Java, ogni classe deriva da una classe Object . Quindi ogni volta che vogliamo passare un riferimento a "qualcosa", possiamo usare un riferimento di tipo Object .

Dato che non abbiamo bisogno di usare i puntatori void per questo scopo in questi linguaggi, void non deve significare "qualcosa o niente" qui.

    
risposta data 22.08.2014 - 23:05
fonte
0

In C, è possibile avere un puntatore a oggetti di qualsiasi tipo [i puntatori si comportano come un tipo di riferimento]. Un puntatore può identificare un oggetto di un tipo noto o identificare un oggetto di tipo arbitrario (sconosciuto). I creatori di C hanno scelto di usare la stessa sintassi generale per "puntatore a cosa di tipo arbitrario" come per "puntatore a cosa di un certo tipo". Poiché la sintassi in quest'ultimo caso richiede un token per specificare qual è il tipo, la sintassi precedente richiede di mettere qualcosa in cui quel token sarebbe appartenuto se il tipo fosse noto. C ha scelto di utilizzare la parola chiave "void" a tale scopo.

In Pascal, come con C, è possibile avere dei puntatori a cose di qualsiasi tipo; dialetti più popolari di Pascal consentono anche "puntatore a cosa di tipo arbitrario", ma piuttosto che usare la stessa sintassi che usano per "puntatore a cosa di un certo tipo", si riferiscono semplicemente al primo come Pointer .

In Java, non ci sono tipi di variabili definite dall'utente; tutto è un riferimento ad oggetti primitivo o heap. Si possono definire cose di tipo "riferimento a un oggetto heap di un tipo particolare" o "riferimento a un oggetto heap di tipo arbitrario". Il primo usa semplicemente il nome del tipo senza alcuna punteggiatura speciale per indicare che si tratta di un riferimento (poiché non può essere nient'altro); il secondo usa il nome del tipo Object .

L'uso di void* come nomenclatura per un puntatore a qualcosa di tipo arbitrario è per quanto ne so univoco rispetto a C e derivate dirette come C ++; altre lingue che conosco gestiscono il concetto semplicemente usando un tipo con un nome distinto.

    
risposta data 23.08.2014 - 21:45
fonte
-1

Puoi pensare alla parola chiave void come a% struct ( In C99 le strutture vuote apparentemente non sono consentite , in .NET e C ++ occupano più di 0 memoria, e infine ricordo che tutto in Java è una classe):

struct void {
};

... che significa che è un tipo con lunghezza 0. Funziona così quando si esegue una chiamata di funzione che restituisce void ... invece di allocare 4 byte o così sullo stack per un valore di ritorno intero, non cambia affatto il puntatore dello stack (lo incrementa di un lunghezza di 0).

Quindi, se hai dichiarato alcune variabili come:

int a;
void b;
int c;

... e poi hai preso l'indirizzo di quelle variabili, allora forse b e c potrebbero trovarsi nello stesso posto in memoria, perché b ha lunghezza zero. Quindi un void* è un puntatore in memoria al tipo built-in con lunghezza zero. Il fatto che tu possa sapere che c'è qualcosa che subito ti potrebbe essere utile è un'altra questione, e richiede di sapere veramente cosa stai facendo (ed è pericoloso).

    
risposta data 25.08.2014 - 16:47
fonte

Leggi altre domande sui tag