Em linguagens fortemente tipadas como Java e C #, void
(ou Void
) como um tipo de retorno para um método parece significar:
Este método não retorna nada. Nada. Sem retorno. Você não receberá nada deste método.
O que é realmente estranho é que em C, void
como um tipo de retorno ou mesmo como um tipo de parâmetro de método, significa:
Realmente poderia ser qualquer coisa. Você precisaria ler o código fonte para descobrir. Boa sorte. Se é um ponteiro, você realmente deve saber o que está fazendo.
Considere os seguintes exemplos em 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, o segundo método retorna um ponteiro, que por definição pode ser qualquer coisa.
Como C é mais antigo que Java e C #, por que essas linguagens adotaram void
como significando "nada" enquanto C usava como "nada ou nada (quando um ponteiro)"?
void
enquanto o exemplo de código usavoid*
algo completamente diferente.Object
em um caso para desambiguar.dynamic
tipo que raramente é usado?Respostas:
A palavra-chave
void
(não um ponteiro) significa "nada" nesses idiomas. Isso é consistente.Como você observou,
void*
significa "ponteiro para qualquer coisa" em idiomas que suportam ponteiros brutos (C e C ++). Essa é uma decisão infeliz, porque, como você mencionou, elavoid
significa duas coisas diferentes.Não consegui encontrar a razão histórica por trás da reutilização
void
para significar "nada" e "qualquer coisa" em contextos diferentes, no entanto, C faz isso em vários outros lugares. Por exemplo,static
tem finalidades diferentes em contextos diferentes. Obviamente, existe um precedente na linguagem C para reutilizar palavras-chave dessa maneira, independentemente do que se possa pensar da prática.Java e C # são diferentes o suficiente para fazer uma pausa limpa para corrigir alguns desses problemas. Java e C # "seguro" também não permitem ponteiros brutos e não precisam de compatibilidade fácil com C (o C # não seguro permite ponteiros, mas a grande maioria do código C # não se enquadra nessa categoria). Isso permite que eles mudem um pouco as coisas sem se preocupar com a compatibilidade com versões anteriores. Uma maneira de fazer isso é introduzir uma classe
Object
na raiz da hierarquia da qual todas as classes são herdadas; portanto, umaObject
referência serve à mesma funçãovoid*
sem a desagradabilidade dos problemas de tipo e gerenciamento de memória não processada.fonte
They also do not allow raw pointers and do not need easy C compatibility.
Falso. C # tem ponteiros. Eles geralmente estão (e geralmente deveriam estar) desligados, mas estão lá.void
: uma razão óbvia seria evitar a introdução de uma nova palavra-chave (e potencialmente interromper os programas existentes). Se eles tivessem introduzido uma palavra-chave especial (por exemplounknown
), entãovoid*
seria uma construção sem sentido (e possivelmente ilegal) eunknown
seria legal apenas na forma deunknown*
.void *
,void
significa "nada", não "nada". Você não pode desreferenciar avoid *
, é necessário convertê-lo primeiro em outro tipo de ponteiro.void
evoid*
são tipos diferentes (na verdade,void
é "nenhum tipo"). Faz tanto sentido quanto dizer "oint
noint*
meio de algo." Não, depois de declarar uma variável de ponteiro, você está dizendo "este é um endereço de memória capaz de armazenar algo". No caso devoid*
, você está dizendo "esse endereço contém algo, mas que algo não pode ser deduzido do tipo de ponteiro".void
evoid*
são duas coisas diferentes.void
em C significa exatamente a mesma coisa que em Java, uma ausência de um valor de retorno. Avoid*
é um ponteiro com uma ausência de um tipo.Todos os ponteiros em C precisam poder ser desreferenciados. Se você referenciasse a
void*
, que tipo você esperaria obter? Lembre-se de que os ponteiros C não carregam nenhuma informação de tipo de tempo de execução, portanto, o tipo deve ser conhecido em tempo de compilação.Dado esse contexto, a única coisa que você pode fazer logicamente com um desreferenciado
void*
é ignorá-lo, que é exatamente o comportamento que ovoid
tipo denota.fonte
gcc
discorda de você. Se você tentar usar o valor,gcc
produza uma mensagem de erro dizendoerror: void value not ignored as it ought to be
.Talvez seja mais útil pensar
void
como o tipo de retorno. Em seguida, seu segundo método seria "um método que retorna um ponteiro sem tipo".fonte
void
é aproximadamente equivalente a*
idiomas como o ActionScript 3, que significa simplesmente "qualquer tipo de retorno".void
não é um curinga. Um ponteiro é apenas um endereço de memória. Colocando um tipo antes de o*
texto diz "aqui está o tipo de dados que você pode encontrar no endereço de memória referido por esse ponteiro". Colocandovoid
antes do que*
diz ao compilador "Eu não sei o tipo; basta retornar o ponteiro bruto e vou descobrir o que fazer com os dados para os quais ele aponta".void*
aponta para um "vazio". Um ponteiro vazio com nada para o qual aponta. Ainda assim, você pode convertê-lo como qualquer outro ponteiro.Vamos reforçar um pouco a terminologia.
No padrão C 2011 online :
Uma
void
expressão não tem valor (embora possa ter efeitos colaterais). Se eu tiver uma função definida para retornarvoid
, assim:então a ligação
não avalia como um valor; Não posso atribuir o resultado a nada, porque não há resultado.
Um ponteiro para
void
é essencialmente um tipo de ponteiro "genérico"; você pode atribuir um valorvoid *
a qualquer outro tipo de ponteiro de objeto sem precisar de uma conversão explícita (e é por isso que todos os programadores C gritarão com você por transmitir o resultado demalloc
).Você não pode desreferenciar diretamente a
void *
; você deve primeiro atribuí-lo a um tipo de ponteiro de objeto diferente antes de poder acessar o objeto apontado.void
ponteiros são usados para implementar (mais ou menos) interfaces genéricas; o exemplo canônico é aqsort
função de biblioteca, que pode classificar matrizes de qualquer tipo, desde que você forneça uma função de comparação com reconhecimento de tipo.Sim, usar a mesma palavra-chave para dois conceitos diferentes (sem valor versus ponteiro genérico) é confuso, mas não é como se não houvesse precedente;
static
tem vários significados distintos em C e C ++.fonte
Essa afirmação está correta. Também está correto para C e C ++.
Essa afirmação está incorreta.
void
como um tipo de retorno em C ou C ++ significa a mesma coisa que em C # e Java. Você está confundindovoid
comvoid*
. Eles são completamente diferentes.Sim.
Um ponteiro é um valor que pode ser desreferenciado . A exclusão de um ponteiro válido fornece um local de armazenamento do tipo apontado . Um ponteiro nulo é um ponteiro que não possui um tipo apontado específico; ele deve ser convertido em um tipo de ponteiro mais específico antes que o valor seja desreferenciado para produzir um local de armazenamento .
Java não possui ponteiros nulos; C # faz. Eles são os mesmos que em C e C ++ - um valor de ponteiro que não possui nenhum tipo específico associado a ele, que deve ser convertido em um tipo mais específico antes de retirá-lo da referência para produzir um local de armazenamento.
A questão é incoerente porque pressupõe falsidades. Vamos fazer algumas perguntas melhores.
Familiarizar-se com os programadores que vêm para Java ou C # de linguagens onde
void
é um tipo de retorno.Familiarizar-se com os programadores que chegam ao C # a partir de idiomas onde
void*
é um tipo de ponteiro.fonte
A primeira função não retorna nada. A segunda função retorna um ponteiro nulo. Eu teria declarado essa segunda função como
Se puder ajudar, se você pensar em "vazio" como significado "sem significado". O valor de retorno de
describe
é "sem significado". Retornar um valor dessa função é ilegal porque você disse ao compilador que o valor de retorno é sem significado. Você não pode capturar o valor de retorno dessa função porque é sem significado. Você não pode declarar uma variável do tipovoid
porque é sem significado. Por exemplo,void nonsense;
é um absurdo e é de fato ilegal. Observe também que uma matriz de nulosvoid void_array[42];
também é um absurdo.Um ponteiro para void (
void*
) é algo diferente. É um ponteiro, não uma matriz. Leiavoid*
como significando um ponteiro que aponta para algo "sem significado". O ponteiro é "sem sentido", o que significa que não faz sentido desreferenciar esse ponteiro. Tentar fazer isso é de fato ilegal. Código que desreferencia um ponteiro nulo não será compilado.Portanto, se você não pode desreferenciar um ponteiro de nulo e não pode fazer uma série de vazios, como pode usá-los? A resposta é que um ponteiro para qualquer tipo pode ser convertido de e para
void*
. Converter um ponteiro paravoid*
e converter um ponteiro para o tipo original produzirá um valor igual ao ponteiro original. O padrão C garante esse comportamento. A principal razão pela qual você vê tantos ponteiros nulos em C é que esse recurso fornece uma maneira de implementar a ocultação de informações e a programação baseada em objetos em uma linguagem que não é orientada a objetos.Finalmente, a razão muitas vezes você vê
move
declarada comovoid *move(...)
mais do quevoid* move(...)
é porque colocar o asterisco ao lado do nome em vez do tipo é uma prática muito comum em C. A razão é que a seguinte declaração vai chegar em apuros:int* iptr, jptr;
Isto faria com que você acho que você está declarando dois ponteiros para umint
. Você não é. Esta declaração fazjptr
um emint
vez de um ponteiro para umint
. A declaração adequada éint *iptr, *jptr;
Você pode fingir que o asterisco pertence ao tipo se você seguir a prática de declarar apenas uma variável por declaração. Mas tenha em mente que você está fingindo.fonte
void* null_ptr = 0;
).Simplificando, não há diferença . Deixe-me dar alguns exemplos.
Digamos que eu tenha uma turma chamada Carro.
Eu acredito que aqui a confusão aqui é baseado fora como você definiria
void
vsvoid*
. Um tipo de retornovoid
significa "Eu não retorno nada", enquanto um tipo de retornovoid*
significa "Retorno um ponteiro do tipo nada".Isso se deve ao fato de um ponteiro ser um caso especial. Um objeto ponteiro sempre apontará para um local na memória, independentemente de existir ou não um objeto válido. Se minhas
void* car
referências a um byte, uma palavra, um carro ou uma bicicleta não importam porque meusvoid*
pontos apontam para algo sem tipo definido. Cabe a nós defini-lo mais tarde.Em linguagens como C # e Java, a memória é gerenciada e o conceito de ponteiros é transferido para a classe Object. Em vez de ter uma referência a um objeto do tipo "nada", sabemos que, no mínimo, nosso objeto é do tipo "Objeto". Deixe-me explicar o porquê.
No C / C ++ convencional, se você criar um objeto e excluir seu ponteiro, será impossível obter acesso a esse objeto. Isso é conhecido como vazamento de memória .
Em C # / Java, tudo é um objeto e isso permite que o tempo de execução acompanhe cada objeto. Ele não precisa necessariamente saber que meu objeto é um carro, ele simplesmente precisa conhecer algumas informações básicas que já são tratadas pela classe Object.
Para nós, programadores, isso significa que muitas das informações que precisaríamos cuidar manualmente em C / C ++ são tratadas por nós pela classe Object, eliminando a necessidade do caso especial que é o ponteiro.
fonte
Tentarei dizer como alguém poderia pensar sobre essas coisas em C. O idioma oficial no padrão C não está muito à vontade
void
e não tentarei ser totalmente consistente com ele.O tipo
void
não é um cidadão de primeira classe entre os tipos. Embora seja um tipo de objeto, não pode ser o tipo de qualquer objeto (ou valor), de um campo ou de um parâmetro de função; no entanto, pode ser o tipo de retorno de uma função e, portanto, o tipo de uma expressão (basicamente de chamadas de tais funções, mas as expressões formadas com o operador condicional também podem ter o tipo nulo). Mas mesmo o uso mínimo devoid
como um tipo de valor pelo qual o item acima deixa a porta aberta, ou seja, encerrar uma função retornandovoid
com uma declaração da forma emreturn E;
queE
é uma expressão do tipovoid
, é explicitamente proibido em C (embora seja permitido em C ++, mas por razões não aplicáveis a C).Se objetos do tipo
void
fossem permitidos, eles teriam 0 bits (que é a quantidade de informações no valor de uma expressão dovoid
tipo); não seria um grande problema permitir esses objetos, mas eles seriam bastante inúteis (objetos de tamanho 0 dificultariam a definição da aritmética dos ponteiros, o que talvez fosse melhor apenas proibido). O conjunto de valores diferentes desse objeto teria um elemento (trivial); seu valor pode ser obtido, trivialmente, porque não possui substância, mas não pode ser modificado (sem valor diferente ). Portanto, o padrão é confuso em dizer quevoid
compreende um conjunto vazio de valores; esse tipo pode ser útil para descrever expressões que não podemser avaliado (como saltos ou chamadas não terminadas), embora a linguagem C não use esse tipo.Ser desaprovado como tipo de valor,
void
foi usado em pelo menos duas maneiras não diretamente relacionadas para designar "proibido": especificar(void)
como especificação de parâmetro em tipos de função significa fornecer qualquer argumento a eles é proibido (enquanto '()', pelo contrário, significaria tudo é permitido e, sim, mesmo fornecendo umavoid
expressão como argumento para funções com(void)
especificação de parâmetro é proibido), e declararvoid *p
significa que a expressão de exclusão de referência*p
é proibida (mas não que seja uma expressão válida do tipovoid
). Portanto, você está certo de que esses usos devoid
não são realmente consistentesvoid
como um tipo válido de expressões. No entanto, um objeto do tipovoid*
não é realmente um ponteiro para valores de qualquer tipo, significa um valor que é tratado como um ponteiro, embora não possa ser desreferenciado e a aritmética do ponteiro seja proibida. O "tratado como um ponteiro" significa apenas que pode ser convertido de e para qualquer tipo de ponteiro sem perda de informações. No entanto, também pode ser usado para converter valores inteiros de e para, para que não precise apontar nada.fonte
void*
para outro tipo de ponteiro pode perder informações (ou até ter UB, não me lembro de imediato), mas se o valor devoid*
veio de converter um ponteiro do mesmo tipo para o qual você está convertendo agora, isso não acontece.Em C # e Java, toda classe é derivada de uma
Object
classe. Portanto, sempre que desejamos passar uma referência a "alguma coisa", podemos usar uma referência do tipoObject
.Como não precisamos usar ponteiros nulos para esse fim nesses idiomas,
void
não precisa significar "algo ou nada" aqui.fonte
Em C, é possível ter um ponteiro para itens de qualquer tipo [os ponteiros se comportam como um tipo de referência]. Um ponteiro pode identificar um objeto de um tipo conhecido ou um objeto de tipo arbitrário (desconhecido). Os criadores de C optaram por usar a mesma sintaxe geral para "ponteiro para coisa de tipo arbitrário" como para "ponteiro para coisa de algum tipo específico". Como a sintaxe no último caso requer um token para especificar qual é o tipo, a sintaxe anterior exige colocar algo onde esse token pertenceria se o tipo fosse conhecido. C optou por usar a palavra-chave "nulo" para esse fim.
Em Pascal, como em C, é possível ter ponteiros para coisas de qualquer tipo; dialetos mais populares de Pascal também permitem "apontador para coisa de tipo arbitrário", mas, em vez de usar a mesma sintaxe usada para "apontador para coisa de algum tipo específico", eles simplesmente se referem ao primeiro como
Pointer
.Em Java, não há tipos de variáveis definidos pelo usuário; tudo é uma referência de objeto primitivo ou heap. Pode-se definir itens do tipo "referência a um objeto de pilha de um tipo específico" ou "referência a um objeto de pilha de tipo arbitrário". O primeiro simplesmente usa o nome do tipo sem nenhuma pontuação especial para indicar que é uma referência (já que não pode ser outra coisa); o último usa o nome do tipo
Object
.O uso de
void*
como nomenclatura para um ponteiro para algo do tipo arbitrário é, até onde sei, exclusivo para C e derivadas diretas como C ++; outras línguas que conheço lidam com o conceito simplesmente usando um tipo de nome distinto.fonte
Você pode pensar na palavra-chave
void
como uma vaziastruct
( no C99, estruturas vazias são aparentemente proibidas , no .NET e C ++ elas ocupam mais de 0 de memória e, por último, lembro que tudo em Java é uma classe):... o que significa que é um tipo com 0 comprimento. É assim que funciona quando você executa uma chamada de função que retorna
void
... em vez de alocar 4 bytes ou mais na pilha para um valor de retorno inteiro, ele não altera o ponteiro da pilha (incrementa-o em um comprimento de 0 )Portanto, se você declarou algumas variáveis como:
... e então você tomou o endereço dessas variáveis, então talvez
b
ec
pode estar localizado no mesmo lugar na memória, porqueb
tem comprimento zero. Portanto, avoid*
é um ponteiro na memória para o tipo interno com comprimento zero. O fato de você saber que há algo imediatamente a seguir que possa ser útil para você é outra questão e exige que você realmente saiba o que está fazendo (e é perigoso).fonte