Dans les langages fortement typés comme Java et C #, void
(ou Void
) comme type de retour pour une méthode semble signifier:
Cette méthode ne renvoie rien. Rien. Non-retour. Vous ne recevrez rien de cette méthode.
Ce qui est vraiment étrange, c'est qu'en C, void
comme type de retour ou même comme type de paramètre de méthode, cela signifie:
Ça pourrait vraiment être n'importe quoi. Il faudrait lire le code source pour le savoir. Bonne chance. Si c'est un pointeur, vous devriez vraiment savoir ce que vous faites.
Considérez les exemples suivants en 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;
}
De toute évidence, la deuxième méthode renvoie un pointeur, qui par définition pourrait être n'importe quoi.
Puisque C est plus ancien que Java et C #, pourquoi ces langages ont-ils adopté void
comme signifiant "rien" alors que C l'a utilisé comme "rien ou rien (quand un pointeur)"?
void
pendant que l'exemple de code utilisevoid*
ce qui est quelque chose de complètement différent.Object
dans un cas pour lever l'ambiguïté.dynamic
type qui est rarement utilisé?Réponses:
Le mot-clé
void
(pas un pointeur) signifie "rien" dans ces langues. C'est cohérent.Comme vous l'avez noté,
void*
signifie «pointeur vers n'importe quoi» dans les langages qui prennent en charge les pointeurs bruts (C et C ++). C'est une décision malheureuse car, comme vous l'avez mentionné, celavoid
signifie deux choses différentes.Je n'ai pas été en mesure de trouver la raison historique derrière la réutilisation
void
pour signifier «rien» et «quoi que ce soit» dans différents contextes, mais C le fait à plusieurs autres endroits. Par exemple,static
a des objectifs différents dans des contextes différents. Il existe évidemment un précédent dans le langage C pour réutiliser des mots clés de cette façon, indépendamment de ce que l'on peut penser de la pratique.Java et C # sont suffisamment différents pour faire une pause nette pour corriger certains de ces problèmes. Java et C # "sûr" n'autorisent pas non plus les pointeurs bruts et n'ont pas besoin d'une compatibilité C facile (C # non sûr autorise les pointeurs mais la grande majorité du code C # ne tombe pas dans cette catégorie). Cela leur permet de changer un peu les choses sans se soucier de la compatibilité descendante. Une façon de procéder consiste à introduire une classe
Object
à la racine de la hiérarchie dont toutes les classes héritent, de sorte qu'uneObject
référence remplit la même fonctionvoid*
sans les problèmes de type et la gestion de la mémoire brute.la source
They also do not allow raw pointers and do not need easy C compatibility.
Faux. C # a des pointeurs. Ils sont généralement (et devraient généralement être) éteints, mais ils sont là.void
: une raison évidente serait d'éviter d'introduire un nouveau mot-clé (et potentiellement de casser les programmes existants). S'ils avaient introduit un mot-clé spécial (par exempleunknown
), alors cevoid*
serait une construction dénuée de sens (et peut-être illégale), etunknown
ne serait légale que sous la forme deunknown*
.void *
,void
signifie «rien», pas «rien». Vous ne pouvez pas déréférencer unvoid *
, vous devez d'abord le convertir en un autre type de pointeur.void
etvoid*
sont de types différents (en fait,void
c'est "pas de type"). Il a autant de sens que de dire « leint
auint*
moyen de quelque chose. » Non, une fois que vous déclarez une variable de pointeur, vous dites "c'est une adresse mémoire capable de contenir quelque chose". Dans le cas devoid*
, vous dites que "cette adresse contient quelque chose mais que quelque chose ne peut pas être déduit du type de pointeur".void
etvoid*
sont deux choses différentes.void
en C signifie exactement la même chose qu'en Java, une absence de valeur de retour. Avoid*
est un pointeur avec une absence de type.Tous les pointeurs en C doivent pouvoir être déréférencés. Si vous déréférenciez a
void*
, quel type vous attendriez-vous à obtenir? N'oubliez pas que les pointeurs C ne contiennent aucune information sur le type d'exécution, donc le type doit être connu au moment de la compilation.Dans ce contexte, la seule chose que vous pouvez logiquement faire avec un déréférencé
void*
est de l'ignorer, ce qui correspond exactement au comportement indiqué par levoid
type.la source
gcc
n'est pas d'accord avec vous. Si vous essayez d'utiliser la valeur,gcc
générez un message d'erreur indiquanterror: void value not ignored as it ought to be
.Il serait peut-être plus utile de penser
void
au type de retour. Ensuite, votre deuxième méthode se lirait «une méthode qui renvoie un pointeur non typé».la source
void
c'est à peu près l'équivalent*
de langages comme ActionScript 3 qui signifie simplement "n'importe quel type de retour".void
n'est pas un caractère générique. Un pointeur n'est qu'une adresse mémoire. Mettre un type avant le*
dit "voici le type de données que vous pouvez vous attendre à trouver à l'adresse mémoire référencée par ce pointeur". Mettrevoid
avant le*
dit au compilateur "Je ne connais pas le type; renvoyez-moi simplement le pointeur brut, et je saurai quoi faire avec les données vers lesquelles il pointe."void*
pointe vers un "vide". Un pointeur nu avec rien qu'il pointe. Vous pouvez toujours le lancer comme n'importe quel autre pointeur.Resserrons un peu la terminologie.
De la norme en ligne C 2011 :
Une
void
expression n'a aucune valeur (bien qu'elle puisse avoir des effets secondaires). Si j'ai une fonction définie pour retournervoid
, comme ceci:puis l'appel
n'évalue pas une valeur; Je ne peux assigner le résultat à rien, car il n'y a pas de résultat.
Un pointeur vers
void
est essentiellement un type de pointeur "générique"; vous pouvez attribuer une valeur devoid *
à tout autre type de pointeur d'objet sans avoir besoin d'une conversion explicite (c'est pourquoi tous les programmeurs C vous crieront après avoir casté le résultat demalloc
).Vous ne pouvez pas déréférencer directement a
void *
; vous devez d'abord l'affecter à un type de pointeur d'objet différent avant de pouvoir accéder à l'objet pointé.void
les pointeurs sont utilisés pour implémenter (plus ou moins) des interfaces génériques; l'exemple canonique est laqsort
fonction de bibliothèque, qui peut trier des tableaux de n'importe quel type, tant que vous fournissez une fonction de comparaison sensible au type.Oui, utiliser le même mot-clé pour deux concepts différents (pas de valeur par rapport au pointeur générique) est source de confusion, mais ce n'est pas comme s'il n'y avait pas de précédent;
static
a plusieurs significations distinctes en C et C ++.la source
Cette affirmation est correcte. Il est également correct pour C et C ++.
Cette déclaration est incorrecte.
void
comme type de retour en C ou C ++ signifie la même chose qu'en C # et Java. Vous confondezvoid
avecvoid*
. Ils sont complètement différents.Oui.
Un pointeur est une valeur qui peut être déréférencée . Le déréférencement d'un pointeur valide donne un emplacement de stockage du type pointé . Un pointeur vide est un pointeur qui n'a pas de type pointé particulier; il doit être converti en un type de pointeur plus spécifique avant de déréférencer la valeur pour produire un emplacement de stockage .
Java n'a pas de pointeurs vides; C # le fait. Ils sont les mêmes qu'en C et C ++ - une valeur de pointeur à laquelle aucun type spécifique n'est associé, qui doit être converti en un type plus spécifique avant de le déréférencer pour produire un emplacement de stockage.
La question est incohérente car elle suppose des mensonges. Posons de meilleures questions.
Être familier aux programmeurs venant en Java ou C # à partir de langages où
void
est un type de retour.Pour être familier aux programmeurs arrivant en C # à partir de langages où
void*
est un type de pointeur.la source
La première fonction ne renvoie rien. La deuxième fonction renvoie un pointeur vide. J'aurais déclaré cette deuxième fonction comme
Cela pourrait aider si vous considérez "vide" comme signifiant "sans signification". La valeur de retour de
describe
est "sans signification". Le retour d'une valeur à partir d'une telle fonction est illégal car vous avez dit au compilateur que la valeur de retour est sans signification. Vous ne pouvez pas capturer la valeur de retour de cette fonction car elle est sans signification. Vous ne pouvez pas déclarer une variable de typevoid
car elle est sans signification. Par exemplevoid nonsense;
est un non-sens et est en fait illégal. Notez également qu'un tableau de videvoid void_array[42];
est également un non-sens.Un pointeur sur void (
void*
) est quelque chose de différent. C'est un pointeur, pas un tableau. A lirevoid*
comme signifiant un pointeur qui pointe vers quelque chose "sans signification". Le pointeur est "sans signification", ce qui signifie qu'il n'est pas logique de déréférencer un tel pointeur. Essayer de le faire est en fait illégal. Le code qui déréférence un pointeur vide ne sera pas compilé.Donc, si vous ne pouvez pas déréférencer un pointeur de vide et que vous ne pouvez pas créer un tableau de vides, comment pouvez-vous les utiliser? La réponse est qu'un pointeur vers n'importe quel type peut être converti vers et depuis
void*
. La conversion d'un pointeurvoid*
et la conversion en un pointeur vers le type d'origine produiront une valeur égale au pointeur d'origine. La norme C garantit ce comportement. La principale raison pour laquelle vous voyez tant de pointeurs vides en C est que cette capacité fournit un moyen d'implémenter le masquage d'informations et la programmation basée sur des objets dans ce qui est essentiellement un langage non orienté objet.Enfin, la raison pour laquelle vous voyez souvent
move
déclaré comme étantvoid *move(...)
plutôt quevoid* move(...)
parce que mettre l'astérisque à côté du nom plutôt que du type est une pratique très courante en C. La raison en est que la déclaration suivante vous posera de gros problèmes:int* iptr, jptr;
cela vous rendrait pensez que vous déclarez deux pointeurs vers unint
. Tu n'es pas. Cette déclaration faitjptr
unint
plutôt qu'un pointeur sur unint
. La déclaration appropriée estint *iptr, *jptr;
Vous pouvez prétendre que l'astérisque appartient au type si vous vous en tenez à ne déclarer qu'une seule variable par instruction de déclaration. Mais gardez à l'esprit que vous faites semblant.la source
void* null_ptr = 0;
).Autrement dit, il n'y a pas de différence . Laisse moi te donner quelques exemples.
Disons que j'ai une classe appelée Car.
Je crois ici la confusion est basé sur la façon dont vous définiriez ici
void
vsvoid*
. Un type de retourvoid
signifie "je ne retourne rien", tandis qu'un type de retourvoid*
signifie "je ne renvoie aucun pointeur de type rien".Cela est dû au fait qu'un pointeur est un cas particulier. Un objet pointeur pointe toujours vers un emplacement en mémoire, qu'il y ait ou non un objet valide. Que mes
void* car
références fassent référence à un octet, à un mot, à une voiture ou à un vélo, cela n'a pas d'importance parce que monvoid*
pointage vers quelque chose sans type défini. C'est à nous de le définir plus tard.Dans des langages tels que C # et Java, la mémoire est gérée et le concept de pointeurs est reporté dans la classe Object. Au lieu d'avoir une référence à un objet de type "rien" nous savons qu'à tout le moins notre objet est de type "Object". Laissez-moi vous expliquer pourquoi.
En C / C ++ conventionnel, si vous créez un objet et supprimez son pointeur, il est impossible d'accéder à cet objet. C'est ce qu'on appelle une fuite de mémoire .
En C # / Java, tout est un objet et cela permet au runtime de garder une trace de chaque objet. Il n'a pas nécessairement besoin de savoir que mon objet est une voiture, il a simplement besoin de connaître certaines informations de base qui sont déjà prises en charge par la classe Object.
Pour nous programmeurs, cela signifie qu'une grande partie des informations que nous aurions à traiter manuellement en C / C ++ sont prises en charge par la classe Object, ce qui supprime la nécessité du cas spécial qu'est le pointeur.
la source
Je vais essayer de dire comment on pourrait penser à ces choses en C. La langue officielle de la norme C n'est pas vraiment à l'aise
void
, et je n'essaierai pas d'être tout à fait cohérente avec elle.Le type
void
n'est pas un citoyen de première classe parmi les types. Bien qu'il s'agisse d'un type d'objet, il ne peut s'agir du type d'aucun objet (ou valeur), d'un champ ou d'un paramètre de fonction; cependant, il peut s'agir du type de retour d'une fonction, et donc du type d'une expression (essentiellement des appels de telles fonctions, mais les expressions formées avec l'opérateur conditionnel peuvent également avoir un type void). Mais même l'utilisation minimale devoid
comme type de valeur pour laquelle ce qui précède laisse la porte ouverte, à savoir terminer une fonction retournantvoid
avec une déclaration de la formereturn E;
oùE
est une expression de typevoid
, est explicitement interdite en C (elle est cependant autorisée en C ++, mais pour des raisons non applicables à C).Si les objets de type
void
étaient autorisés, ils auraient 0 bits (c'est-à-dire la quantité d'informations dans la valeur d'une expression devoid
type); ce ne serait pas un problème majeur d'autoriser de tels objets, mais ils seraient plutôt inutiles (les objets de taille 0 rendraient difficile la définition de l'arithmétique des pointeurs, ce qui serait peut-être préférable d'être interdit). L'ensemble des différentes valeurs d'un tel objet aurait un élément (trivial); sa valeur pourrait être prise, trivialement car elle n'a pas de substance, mais elle ne peut pas être modifiée (sans valeur différente ). La norme est donc confuse en disant qu'ellevoid
comprend un ensemble vide de valeurs; un tel type pourrait être utile pour décrire des expressions qui ne peuvent pasêtre évalué (comme les sauts ou les appels sans terminaison) bien que le langage C n'emploie pas réellement un tel type.Le fait d'être interdit comme type de valeur
void
a été utilisé d'au moins deux façons non directement liées pour désigner «interdit»: la spécification(void)
comme spécification de paramètre dans les types de fonction signifie qu'il est interdit de leur fournir des arguments (tandis que «()» signifie au contraire tout est autorisé; et oui, même fournir unevoid
expression comme argument aux fonctions avec(void)
spécification de paramètre est interdit), et déclarervoid *p
signifie que l'expression de déréférencement*p
est interdite (mais pas qu'il s'agit d'une expression valide de typevoid
). Vous avez donc raison de dire que ces utilisations devoid
ne sont pas vraiment compatibles avecvoid
un type d'expressions valide. Cependant un objet de typevoid*
n'est pas vraiment un pointeur vers des valeurs de n'importe quel type, cela signifie une valeur qui est traitée comme un pointeur bien qu'elle ne puisse pas être déréférencée et l'arithmétique du pointeur avec elle est interdite. Le «traité comme un pointeur» signifie alors uniquement qu'il peut être converti depuis et vers n'importe quel type de pointeur sans perte d'informations. Cependant, il peut également être utilisé pour convertir des valeurs entières vers et depuis, il n'a donc pas à pointer du tout.la source
void*
vers un autre type de pointeur peut perdre des informations (ou même avoir UB, je ne m'en souviens pas), mais si la valeur de lavoid*
provenait de la conversion d'un pointeur du même type vers lequel vous effectuez la conversion, alors ce n'est pas le cas.En C # et Java, chaque classe est dérivée d'une
Object
classe. Par conséquent, chaque fois que nous souhaitons passer une référence à "quelque chose", nous pouvons utiliser une référence de typeObject
.Comme nous n'avons pas besoin d'utiliser des pointeurs void à cet effet dans ces langues,
void
cela ne signifie pas nécessairement «quelque chose ou rien» ici.la source
En C, il est possible d'avoir un pointeur vers des objets de tout type [les pointeurs se comportent comme un type de référence]. Un pointeur peut identifier un objet d'un type connu, ou il peut identifier un objet de type arbitraire (inconnu). Les créateurs de C ont choisi d'utiliser la même syntaxe générale pour "pointeur vers une chose de type arbitraire" que pour "pointeur vers une chose d'un type particulier". Étant donné que la syntaxe dans ce dernier cas nécessite un jeton pour spécifier le type, l'ancienne syntaxe nécessite de placer quelque chose à l'endroit où ce jeton appartiendrait si le type était connu. C a choisi d'utiliser le mot clé "void" à cette fin.
En Pascal, comme en C, il est possible d'avoir des pointeurs vers des choses de tout type; les dialectes plus populaires de Pascal permettent également de "pointer vers une chose de type arbitraire", mais plutôt que d'utiliser la même syntaxe qu'ils utilisent pour "pointer vers une chose d'un type particulier", ils se réfèrent simplement à la première comme
Pointer
.En Java, il n'y a pas de types de variables définis par l'utilisateur; tout est soit une primitive soit une référence d'objet de tas. On peut définir des choses de type "référence à un objet tas d'un type particulier" ou "référence à un objet tas de type arbitraire". Le premier utilise simplement le nom du type sans aucune ponctuation spéciale pour indiquer qu'il s'agit d'une référence (car il ne peut pas être autre chose); ce dernier utilise le nom du type
Object
.L'utilisation de
void*
comme nomenclature pour un pointeur vers quelque chose de type arbitraire est pour autant que je sache unique au C et aux dérivés directs comme C ++; d'autres langues que je connais gèrent le concept en utilisant simplement un type au nom distinct.la source
Vous pouvez en quelque sorte penser le mot-clé
void
comme un videstruct
( en C99, les structures vides sont apparemment interdites , en .NET et C ++, elles occupent plus de 0 mémoire, et enfin je rappelle que tout en Java est une classe):... ce qui signifie que c'est un type de longueur 0. Voici comment cela fonctionne lorsque vous effectuez un appel de fonction qui renvoie
void
... au lieu d'allouer environ 4 octets à la pile pour une valeur de retour entière, cela ne change pas du tout le pointeur de la pile (l'incrémente d'une longueur de 0 ).Donc, si vous avez déclaré certaines variables comme:
... et puis vous avez pris l'adresse de ces variables, alors peut
b
- être etc
peut-être situé au même endroit en mémoire, carb
a une longueur nulle. Donc, avoid*
est un pointeur en mémoire vers le type intégré de longueur nulle. Le fait que vous sachiez qu'il y a quelque chose immédiatement après cela qui pourrait vous être utile est une autre affaire et vous oblige à vraiment savoir ce que vous faites (et c'est dangereux).la source