En lenguajes fuertemente tipados como Java y C #, void
(o Void
) como tipo de retorno para un método parecen significar:
Este método no devuelve nada. Nada. Sin retorno. No recibirá nada de este método.
Lo realmente extraño es que en C, void
como un tipo de retorno o incluso como un tipo de parámetro de método significa:
Realmente podría ser cualquier cosa. Tendrías que leer el código fuente para averiguarlo. Buena suerte. Si es un puntero, realmente deberías saber lo que estás haciendo.
Considere los siguientes ejemplos 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;
}
Obviamente, el segundo método devuelve un puntero, que por definición podría ser cualquier cosa.
Dado que C es más antiguo que Java y C #, ¿por qué estos lenguajes adoptaron void
como "nada" mientras que C lo usó como "nada o nada (cuando un puntero)"?
void
mientras que el ejemplo de código usavoid*
algo completamente diferente.Object
en un caso para desambiguar.dynamic
tipo que rara vez se usa?Respuestas:
La palabra clave
void
(no un puntero) significa "nada" en esos idiomas. Esto es consistenteComo notó,
void*
significa "puntero a cualquier cosa" en lenguajes que admiten punteros sin formato (C y C ++). Esta es una decisión desafortunada porque, como mencionaste,void
significa dos cosas diferentes.No he podido encontrar la razón histórica detrás de la reutilización
void
para significar "nada" y "nada" en diferentes contextos, sin embargo, C lo hace en varios otros lugares. Por ejemplo,static
tiene diferentes propósitos en diferentes contextos. Obviamente, hay un precedente en el lenguaje C para reutilizar palabras clave de esta manera, independientemente de lo que uno pueda pensar de la práctica.Java y C # son lo suficientemente diferentes como para hacer un corte limpio para corregir algunos de estos problemas. Java y C # "seguro" tampoco permiten punteros sin procesar y no necesitan compatibilidad con C fácil (C # inseguro sí permite punteros, pero la gran mayoría del código de C # no entra en esta categoría). Esto les permite cambiar un poco las cosas sin preocuparse por la compatibilidad con versiones anteriores. Una forma de hacerlo es introducir una clase
Object
en la raíz de la jerarquía de la que heredan todas las clases, por lo que unaObject
referencia cumple la misma funciónvoid*
sin la maldad de los problemas de tipo y la administración de memoria sin procesar.fuente
They also do not allow raw pointers and do not need easy C compatibility.
Falso. C # tiene punteros. Usualmente están (y usualmente deberían estar) apagados, pero están ahí.void
: una razón obvia sería evitar introducir una nueva palabra clave (y potencialmente romper los programas existentes). Si hubieran introducido una palabra clave especial (punknown
. Ej. ),void*
Sería una construcción sin sentido (y posiblemente ilegal), yunknown
sería legal solo en forma deunknown*
.void *
,void
significa "nada", no "nada". No puede desreferenciar avoid *
, primero debe convertirlo a otro tipo de puntero.void
yvoid*
son diferentes tipos (en realidad,void
es "sin tipo"). Tiene tanto sentido como decir "loint
inint*
significa algo". No, una vez que declaras una variable de puntero estás diciendo "esta es una dirección de memoria capaz de contener algo". En el caso devoid*
, está diciendo que "esta dirección contiene algo pero que algo no puede inferirse del tipo de puntero".void
yvoid*
son dos cosas diferentes.void
en C significa exactamente lo mismo que en Java, la ausencia de un valor de retorno. Avoid*
es un puntero con ausencia de un tipo.Todos los punteros en C necesitan poder ser desreferenciados. Si desreferenciara a
void*
, ¿qué tipo esperaría obtener? Recuerde que los punteros en C no llevan ninguna información de tipo de tiempo de ejecución, por lo que el tipo debe conocerse en el momento de la compilación.Dado ese contexto, lo único que puede hacer lógicamente con un desreferenciado
void*
es ignorarlo, que es exactamente el comportamiento quevoid
denota el tipo.fuente
gcc
no está de acuerdo contigo. Si intenta usar el valor,gcc
genere un mensaje de error que digaerror: void value not ignored as it ought to be
.Quizás sería más útil pensar
void
en el tipo de retorno. Luego, su segundo método sería "un método que devuelve un puntero sin tipo".fuente
void
es más o menos equivalente a*
lenguajes como ActionScript 3, que simplemente significa "cualquier tipo de retorno".void
no es un comodín. Un puntero es solo una dirección de memoria. Poner un tipo antes del*
dice "aquí está el tipo de datos que puede esperar encontrar en la dirección de memoria a la que hace referencia este puntero". Poniendovoid
antes el dicho*
al compilador "No sé el tipo; solo devuélveme el puntero sin procesar y descubriré qué hacer con los datos a los que apunta".void*
apunta a un "vacío". Un puntero desnudo sin nada a lo que apunta. Aún así puedes lanzarlo como cualquier otro puntero.Vamos a ajustar un poco la terminología.
Del estándar C 2011 en línea :
Una
void
expresión no tiene valor (aunque puede tener efectos secundarios). Si tengo una función definida para devolvervoid
, así:entonces la llamada
no evalúa a un valor; No puedo asignar el resultado a nada, porque no hay resultado.
Un puntero a
void
es esencialmente un tipo de puntero "genérico"; puede asignar un valorvoid *
a cualquier otro tipo de puntero de objeto sin necesidad de una conversión explícita (razón por la cual todos los programadores de C le gritarán por emitir el resultado demalloc
).No puede desreferenciar directamente un
void *
; primero debe asignarlo a un tipo de puntero de objeto diferente antes de poder acceder al objeto señalado.void
los punteros se utilizan para implementar (más o menos) interfaces genéricas; El ejemplo canónico es laqsort
función de biblioteca, que puede ordenar matrices de cualquier tipo, siempre que proporcione una función de comparación con reconocimiento de tipo.Sí, usar la misma palabra clave para dos conceptos diferentes (sin valor versus puntero genérico) es confuso, pero no es que no haya precedentes;
static
tiene múltiples significados distintos en C y C ++.fuente
Esa afirmación es correcta. También es correcto para C y C ++.
Esa afirmación es incorrecta.
void
como tipo de retorno en C o C ++ significa lo mismo que hace en C # y Java. Estás confundiendovoid
convoid*
. Son completamente diferentesSí.
Un puntero es un valor que puede ser desreferenciado . Anular la referencia a un puntero válido proporciona una ubicación de almacenamiento del tipo apuntado . Un puntero vacío es un puntero que no tiene ningún tipo de apuntamiento particular; debe convertirse a un tipo de puntero más específico antes de desreferenciar el valor para producir una ubicación de almacenamiento .
Java no tiene punteros vacíos; C # hace. Son los mismos que en C y C ++: un valor de puntero que no tiene ningún tipo específico asociado, que debe convertirse a un tipo más específico antes de desreferenciarlo para producir una ubicación de almacenamiento.
La pregunta es incoherente porque presupone falsedades. Hagamos algunas mejores preguntas.
Estar familiarizado con los programadores que vienen a Java o C # desde lenguajes donde
void
es un tipo de retorno.Conocer a los programadores que llegan a C # desde lenguajes donde
void*
hay un tipo de puntero.fuente
La primera función no devuelve nada. La segunda función devuelve un puntero vacío. Hubiera declarado esa segunda función como
Podría ayudar si piensa en "vacío" como que significa "sin significado". El valor de retorno de
describe
es "sin significado". Devolver un valor de dicha función es ilegal porque le dijo al compilador que el valor devuelto no tiene sentido. No puede capturar el valor de retorno de esa función porque no tiene sentido. No puede declarar una variable de tipovoid
porque no tiene sentido. Por ejemplo, novoid nonsense;
tiene sentido y de hecho es ilegal. También tenga en cuenta que una matriz de vacíovoid void_array[42];
también es una tontería.Un puntero a void (
void*
) es algo diferente. Es un puntero, no una matriz. Leervoid*
como significado un puntero que apunta a algo "sin significado". El puntero es "sin significado", lo que significa que no tiene sentido desreferenciar dicho puntero. Intentar hacerlo es ilegal. El código que desreferencia un puntero vacío no se compilará.Entonces, si no puede desreferenciar un puntero vacío, y no puede hacer una serie de vacíos, ¿cómo puede usarlos? La respuesta es que un puntero a cualquier tipo se puede lanzar hacia y desde
void*
. Alvoid*
lanzar un puntero y volver a un puntero al tipo original, se obtendrá un valor igual al puntero original. El estándar C garantiza este comportamiento. La razón principal por la que ve tantos punteros vacíos en C es que esta capacidad proporciona una forma de implementar la ocultación de información y la programación basada en objetos en un lenguaje muy orientado a objetos.Finalmente, la razón por la que a menudo se ve
move
declarado como envoid *move(...)
lugar devoid* move(...)
porque es poner el asterisco al lado del nombre en lugar del tipo es una práctica muy común en C. La razón es que la siguiente declaración te meterá en un gran problema:int* iptr, jptr;
esto te haría cree que está declarando dos punteros a unoint
. Usted no. Esta declaración hacejptr
unint
más que un puntero a unint
. La declaración adecuada esint *iptr, *jptr;
: puede fingir que el asterisco pertenece al tipo si se apega a la práctica de declarar solo una variable por declaración de declaración. Pero tenga en cuenta que está fingiendo.fuente
void* null_ptr = 0;
).En pocas palabras, no hay diferencia . Deja que te dé algunos ejemplos.
Digamos que tengo una clase llamada Car.
Creo que aquí la confusión en este punto se basa apagado cómo definiría
void
vsvoid*
. Un tipo de retornovoid
significa "no devuelvo nada", mientras que un tipo de retornovoid*
significa "devuelvo un puntero de tipo nada".Esto se debe al hecho de que un puntero es un caso especial. Un objeto puntero siempre apuntará a una ubicación en la memoria, ya sea que haya un objeto válido allí o no. Si mis
void* car
referencias a un byte, una palabra, un automóvil o una bicicleta no importan porque misvoid*
puntos apuntan a algo sin un tipo definido. Depende de nosotros definirlo más adelante.En lenguajes como C # y Java, la memoria se gestiona y el concepto de punteros se transfiere a la clase Object. En lugar de tener una referencia a un objeto de tipo "nada", sabemos que al menos nuestro objeto es de tipo "Objeto". Déjame explicarte por qué.
En C / C ++ convencional, si crea un objeto y elimina su puntero, entonces es imposible obtener acceso a ese objeto. Esto es lo que se conoce como pérdida de memoria .
En C # / Java, todo es un objeto y esto permite que el tiempo de ejecución realice un seguimiento de cada objeto. No necesariamente necesita saber que mi objeto es un automóvil, simplemente necesita conocer información básica que ya está a cargo de la clase Object.
Para nosotros, los programadores, eso significa que gran parte de la información que tendríamos que cuidar manualmente en C / C ++ nos ocupa la clase Object, eliminando la necesidad del caso especial que es el puntero.
fuente
Trataré de decir cómo se podría pensar en estas cosas en C. El lenguaje oficial en el estándar C no está realmente a gusto
void
, y no trataré de ser completamente consistente con él.El tipo
void
no es un ciudadano de primera clase entre los tipos. Aunque es un tipo de objeto, no puede ser el tipo de ningún objeto (o valor), de un campo o de un parámetro de función; sin embargo, puede ser el tipo de retorno de una función y, por lo tanto, puede ser el tipo de una expresión (básicamente de llamadas de tales funciones, pero las expresiones formadas con el operador condicional también pueden tener un tipo de vacío). Pero incluso el uso mínimo devoid
un tipo de valor para el que lo anterior deja la puerta abierta, es decir, finalizar una función que regresavoid
con una declaración de la formareturn E;
dondeE
es una expresión de tipovoid
, está explícitamente prohibido en C (aunque está permitido en C ++, pero por razones no aplicables a C).Si
void
se permitieran objetos de tipo , tendrían 0 bits (es decir, la cantidad de información en el valor de una expresión devoid
tipo); no sería un problema importante permitir tales objetos, pero serían bastante inútiles (los objetos de tamaño 0 darían cierta dificultad para definir la aritmética del puntero, lo que quizás sería mejor prohibir). El conjunto de valores diferentes de dicho objeto tendría un elemento (trivial); su valor podría tomarse, trivialmente porque no tiene ninguna sustancia, pero no puede modificarse (sin ningún valor diferente ). Por lo tanto, el estándar se confunde al decir quevoid
comprende un conjunto vacío de valores; dicho tipo podría ser útil para describir expresiones que no puedenser evaluado (como saltos o llamadas que no terminan) aunque el lenguaje C no emplea tal tipo.Al ser rechazado como tipo de valor,
void
se ha utilizado al menos en dos formas no directamente relacionadas para designar "prohibido": especificar(void)
como especificación de parámetros en tipos de función significa proporcionar cualquier argumento para ellos (mientras que '()' significaría todo lo contrario está permitido; y sí, incluso proporcionar unavoid
expresión como argumento para funciones con(void)
especificación de parámetros está prohibido), y declararvoid *p
significa que la expresión de desreferenciación*p
está prohibida (pero no es que sea una expresión de tipo válidavoid
). Entonces tiene razón en que estos usos devoid
no son realmente consistentesvoid
como un tipo válido de expresiones. Sin embargo, un objeto de tipovoid*
no es realmente un puntero a valores de ningún tipo, significa un valor que se trata como un puntero, aunque no se puede desreferenciar y se prohíbe la aritmética del puntero. El "tratado como un puntero" en realidad solo significa que puede emitirse desde y hacia cualquier tipo de puntero sin pérdida de información. Sin embargo, también se puede usar para emitir valores enteros hacia y desde, por lo que no tiene que apuntar a nada en absoluto.fuente
void*
otro tipo de puntero puede perder información (o incluso tener UB, no puedo recordarlo de la mano), pero si el valor del resultadovoid*
proviene de lanzar un puntero del mismo tipo al que ahora está lanzando, entonces no lo haceEn C # y Java, cada clase se deriva de una
Object
clase. Por lo tanto, cada vez que deseamos pasar una referencia a "algo", podemos usar una referencia de tipoObject
.Dado que no necesitamos usar punteros vacíos para ese propósito en estos idiomas,
void
no tiene que significar "algo o nada" aquí.fuente
En C, es posible tener un puntero a cosas de cualquier tipo [los punteros se comportan como un tipo de referencia]. Un puntero puede identificar un objeto de un tipo conocido, o puede identificar un objeto de tipo arbitrario (desconocido). Los creadores de C eligieron usar la misma sintaxis general para "puntero a cosa de tipo arbitrario" que para "puntero a cosa de algún tipo particular". Dado que la sintaxis en el último caso requiere un token para especificar cuál es el tipo, la sintaxis anterior requiere poner algo donde pertenecería ese token si se conociera el tipo. C eligió usar la palabra clave "void" para ese propósito.
En Pascal, como con C, es posible tener punteros a cosas de cualquier tipo; los dialectos más populares de Pascal también permiten "puntero a cosa de tipo arbitrario", pero en lugar de usar la misma sintaxis que usan para "puntero a cosa de algún tipo particular", simplemente se refieren al primero como
Pointer
.En Java, no hay tipos de variables definidas por el usuario; todo es una referencia de objeto primitivo o de montón. Se pueden definir cosas de tipo "referencia a un objeto de montón de un tipo particular" o "referencia a un objeto de montón de tipo arbitrario". El primero simplemente usa el nombre del tipo sin puntuación especial para indicar que es una referencia (ya que no puede ser otra cosa); este último usa el nombre del tipo
Object
.El uso de
void*
como nomenclatura para un puntero a algo de tipo arbitrario es, hasta donde yo sé, exclusivo de C y derivados directos como C ++; otros idiomas que conozco manejan el concepto simplemente usando un tipo con un nombre distintivo.fuente
Puede pensar en la palabra clave
void
como un vacíostruct
( en C99 las estructuras vacías aparentemente no están permitidas , en .NET y C ++ ocupan más de 0 memoria, y por último recuerdo que todo en Java es una clase):... lo que significa que es un tipo con longitud 0. Así es como funciona cuando realiza una llamada de función que devuelve
void
... en lugar de asignar 4 bytes más o menos en la pila para un valor de retorno entero, no cambia el puntero de la pila (lo incrementa en una longitud de 0 )Entonces, si declaró algunas variables como:
... y luego tomaste la dirección de esas variables, entonces quizás
b
yc
podría ubicarse en el mismo lugar en la memoria, porqueb
tiene longitud cero. Entonces avoid*
es un puntero en memoria al tipo incorporado con longitud cero. El hecho de que puedas saber que hay algo inmediatamente después que puede ser útil para ti es otro asunto y requiere que realmente sepas lo que estás haciendo (y es peligroso).fuente