Conceito de ponteiro vazio na programação C

129

É possível desreferenciar um ponteiro nulo sem conversão de tipo na linguagem de programação C?

Além disso, existe alguma maneira de generalizar uma função que possa receber um ponteiro e armazená-la em um ponteiro vazio e, usando esse ponteiro vazio, podemos criar uma função generalizada?

por exemplo:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

Eu quero tornar essa função genérica sem usar instruções if-else - isso é possível?

Além disso, se houver bons artigos na Internet que expliquem o conceito de ponteiro nulo, seria benéfico se você pudesse fornecer os URLs.

Além disso, é possível aritmética de ponteiro com ponteiros nulos?

Um geek
fonte

Respostas:

97

É possível desreferenciar o ponteiro nulo sem conversão de tipo na linguagem de programação C ...

Não, voidindica a ausência de tipo, não é algo que você possa desreferenciar ou atribuir.

existe alguma maneira de generalizar uma função que possa receber ponteiro e armazená-la no ponteiro nulo e, usando esse ponteiro nulo, podemos criar uma função generalizada.

Você não pode simplesmente desdiferenciá-lo de maneira portátil, pois ele pode não estar alinhado corretamente. Pode ser um problema em algumas arquiteturas como ARM, em que o ponteiro para um tipo de dados deve estar alinhado no limite do tamanho do tipo de dados (por exemplo, o ponteiro para um número inteiro de 32 bits deve estar alinhado no limite de 4 bytes para ser desreferenciado).

Por exemplo, lendo uint16_tem void*:

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

Além disso, é possível aritmética de ponteiro com ponteiros nulos ...

A aritmética do ponteiro não é possível em ponteiros voiddevido à falta de valor concreto abaixo do ponteiro e, portanto, ao tamanho.

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */
Alex B
fonte
2
A menos que eu me lembre incorretamente, você pode desreferenciar com segurança um vazio * de duas maneiras. A conversão para char * é sempre aceitável e, se você souber o tipo original, ele indica que você pode converter para esse tipo. O void * perdeu as informações de tipo e, portanto, precisa ser armazenado em outro local.
1111 Dan Danson
1
Sim, você sempre pode desreferenciar se converter para outro tipo, mas não poderá fazer o mesmo se não transmitir. Em resumo, o compilador não saberá quais instruções de montagem usar para operações matemáticas até que você o faça a conversão.
Zachary Hamm
20
Eu acho que o GCC trata aritmética nos void *ponteiros da mesma forma char *, mas não é padrão e você não deve confiar nisso.
315 ephemient
5
Não tenho certeza se sigo. No exemplo, o OP listado acima dos tipos recebidos é garantido como correto pelo usuário da função. Você está dizendo que, se tivermos algo em que atribuirmos um valor de tipo conhecido a um ponteiro, faça o lançamento para um ponteiro nulo e depois o faça de volta ao tipo de ponteiro original, para que ele fique desalinhado? Não entendo por que o compilador o prejudicaria assim, desalinhando coisas aleatoriamente, simplesmente porque você as lança em um ponteiro nulo. Ou você está simplesmente dizendo que não podemos confiar no usuário para saber o tipo de argumento que ele passou para a função?
Doliver
2
@doliver eu quis dizer # 2 ("não podemos confiar no usuário para saber o tipo de argumento que ele passou para a função"). Mas revisitando minha resposta de 6 anos atrás (ugh, faz realmente tanto tempo?) Entendo o que você quer dizer, nem sempre é o caso: quando o ponteiro original apontava para o tipo adequado, ele já estava alinhado, portanto seria seguro transmitir sem problemas de alinhamento.
21715 Alex B:
31

Em C, a void *pode ser convertido em um ponteiro para um objeto de um tipo diferente sem uma conversão explícita:

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

Isso não ajuda a escrever sua função de uma maneira mais genérica.

Você não pode desreferenciar um void * com a conversão para um tipo de ponteiro diferente, pois desreferenciar um ponteiro está obtendo o valor do objeto apontado. Um nu voidnão é um tipo válido; portanto, void *não é possível desmarcá-lo .

A aritmética do ponteiro trata da alteração dos valores do ponteiro por múltiplos dos sizeofobjetos apontados. Novamente, por voidnão ser um tipo verdadeiro, sizeof(void)não tem significado, portanto a aritmética do ponteiro não é válida void *. (Algumas implementações permitem, usando a aritmética equivalente do ponteiro para char *.)

CB Bailey
fonte
1
@CharlesBailey No seu código, você escreve void abc(void *a, int b). Mas por que não usar void abc(int *a, int b), já que em sua função você finalmente definirá int *test = a;, ela salvará uma variável e não vejo outras vantagens definindo outra variável. Vejo muitos códigos gravados dessa maneira usando void *como parâmetros e lançando variáveis ​​posteriormente na função. Mas como essa função é escrita pelo autor, ele deve conhecer o uso da variável; portanto, por que usar void *? Obrigado
Lion Lai
15

Você deve estar ciente de que em C, diferente de Java ou C #, não há absolutamente nenhuma possibilidade de "adivinhar" com êxito o tipo de objeto void*apontado por um ponteiro. Algo semelhante a getClass()simplesmente não existe, já que essas informações não podem ser encontradas. Por esse motivo, o tipo de "genérico" que você procura sempre vem com metainformação explícita, como int bno exemplo ou na string de formato na printffamília de funções.

Erich Kitzmueller
fonte
7

Um ponteiro nulo é conhecido como ponteiro genérico, que pode se referir a variáveis ​​de qualquer tipo de dados.

Sneha
fonte
6

Até agora, meu entendimento sobre o ponteiro nulo é o seguinte.

Quando uma variável de ponteiro é declarada usando a palavra-chave void - ela se torna uma variável de ponteiro de uso geral. O endereço de qualquer variável de qualquer tipo de dados (char, int, float etc.) pode ser atribuído a uma variável de ponteiro nulo.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Como outro ponteiro de tipo de dados pode ser atribuído ao ponteiro nulo, usei-o na função absolut_value (código mostrado abaixo). Para fazer uma função geral.

Eu tentei escrever um código C simples que toma inteiro ou flutua como argumento e tenta torná-lo positivo, se negativo. Eu escrevi o seguinte código,

#include<stdio.h>

void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
{
    if ( *j < 0 )
        *j = *j * (-1);

}

int main()
{
    int i = 40;
    float f = -40;
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    absolute_value(&i);
    absolute_value(&f);
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    return 0;
}   

Mas eu estava tendo erro, então cheguei a saber que meu entendimento com o ponteiro nulo não está correto :(. Então agora vou avançar para coletar pontos por que isso acontece.

As coisas que eu preciso entender mais sobre ponteiros nulos é isso.

Precisamos converter a variável void pointer para desreferenciá-la. Isso ocorre porque um ponteiro nulo não possui um tipo de dados associado a ele. Não há como o compilador saber (ou adivinhar?) Que tipo de dados é apontado pelo ponteiro nulo. Portanto, para pegar os dados apontados por um ponteiro nulo, nós o compilamos com o tipo correto de dados contidos no local dos ponteiros nulos.

void main()

{

    int a=10;

    float b=35.75;

    void *ptr; // Declaring a void pointer

    ptr=&a; // Assigning address of integer to void pointer.

    printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.

    ptr=&b; // Assigning address of float to void pointer.

    printf("The value of float variable is= %f",*( (float*) ptr) );

}

Um ponteiro nulo pode ser realmente útil se o programador não tiver certeza sobre o tipo de dado inserido pelo usuário final. Nesse caso, o programador pode usar um ponteiro nulo para apontar para o local do tipo de dados desconhecido. O programa pode ser configurado de forma a solicitar ao usuário que informe o tipo de dados e a conversão do tipo pode ser realizada de acordo com as informações inseridas pelo usuário. Um trecho de código é fornecido abaixo.

void funct(void *a, int z)
{
    if(z==1)
    printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
    else if(z==2)
    printf("%c",*(char*)a); // Typecasting for character pointer.
    else if(z==3)
    printf("%f",*(float*)a); // Typecasting for float pointer
}

Outro ponto importante que você deve ter em mente sobre os ponteiros nulos é que - a aritmética do ponteiro não pode ser executada em um ponteiro nulo.

void *ptr;

int a;

ptr=&a;

ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.

Então agora eu entendi qual foi o meu erro. Estou corrigindo o mesmo.

Referências :

http://www.antoarts.com/void-pointers-in-c/

http://www.circuitstoday.com/void-pointers-in-c .

O novo código é como mostrado abaixo.


#include<stdio.h>
#define INT 1
#define FLOAT 2

void absolute_value ( void *j, int *n)
{
    if ( *n == INT) {
        if ( *((int*)j) < 0 )
            *((int*)j) = *((int*)j) * (-1);
    }
    if ( *n == FLOAT ) {
        if ( *((float*)j) < 0 )
            *((float*)j) = *((float*)j) * (-1);
    }
}


int main()
{
    int i = 0,n=0;
    float f = 0;
    printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
    scanf("%d",&n);
    printf("\n");
    if( n == 1) {
        scanf("%d",&i);
        printf("value entered before absolute function exec = %d \n",i);
        absolute_value(&i,&n);
        printf("value entered after absolute function exec = %d \n",i);
    }
    if( n == 2) {
        scanf("%f",&f);
        printf("value entered before absolute function exec = %f \n",f);
        absolute_value(&f,&n);
        printf("value entered after absolute function exec = %f \n",f);
    }
    else
    printf("unknown entry try again\n");
    return 0;
}   

Obrigado,

Megharaj
fonte
2

Não, não é possível. Que tipo o valor não referenciado deve ter?

zvrba
fonte
2
void abc(void *a, int b) {
  char *format[] = {"%d", "%c", "%f"};
  printf(format[b-1], a);
}

fonte
Este programa é possível .... Por favor, verifique, se isso for possível na programação C ...
AGeek
2
Sim, isso é possível (embora o código seja perigoso sem uma verificação de alcance para b). Printf recebe um número variável de argumentos e a é apenas pressionado na pilha como qualquer ponteiro (sem nenhuma informação de tipo) e exibido dentro da função printf usando macros va_arg usando informações da string de formato.
hlovdal
@SiegeX: descreva o que não é portátil e o que pode ser indefinido.
Gauthier
@ Gauthier passar uma variável que contém especificadores de formato não é um comportamento portável e potencialmente indefinido. Se você quiser fazer algo assim, veja o sabor 'vs' de printf. Esta resposta tem um bom exemplo disso.
SiegeX 1/11/11
Na prática, eu provavelmente substituiria int bpor um enum, mas fazer qualquer alteração posterior nesse enum interromperia essa função.
Jack Stout
1

Eu quero tornar essa função genérica, sem usar ifs; é possível?

A única maneira simples que vejo é usar a sobrecarga .. que não está disponível no idioma de programação C AFAIK.

Você considerou o idioma de programação C ++ para o seu programa? Ou existe alguma restrição que proíba seu uso?

yves Baumes
fonte
Ok, isso é uma vergonha. Do meu ponto de vista, não vejo soluções então. Na verdade, é o mesmo que printf () & cout: 2 maneiras diferentes de implementar a impressão. printf () o uso se () declaração de decodificar a seqüência de formato (suponho ou algo similar), enquanto que para o operador caso cout << é uma função sobrecarregada
Yves Baumes
1

Aqui está um breve ponteiro sobre voidponteiros: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Ponteiros nulos

Como o ponteiro nulo não sabe para que tipo de objeto está apontando, ele não pode ser desreferenciado diretamente! Em vez disso, o ponteiro nulo deve primeiro ser explicitamente convertido para outro tipo de ponteiro antes de ser desreferenciado.

Se um ponteiro nulo não sabe o que está apontando, como sabemos para o que lançá-lo? Em última análise, cabe a você acompanhar.

Ponteiro vazio miscelânea

Não é possível fazer aritmética de ponteiro em um ponteiro vazio. Isso ocorre porque a aritmética do ponteiro exige que o ponteiro saiba para qual tamanho de objeto está apontando, para que ele possa aumentar ou diminuir o ponteiro adequadamente.

Supondo que a memória da máquina seja endereçável por byte e não exija acessos alinhados, a maneira mais genérica e atômica (mais próxima da representação no nível da máquina) de interpretar a void*é como um ponteiro para um byte uint8_t*. Converter um void*para a uint8_t*permitiria, por exemplo, imprimir os primeiros 1/2/4/8 / quantos você desejar, começando nesse endereço, mas você não pode fazer muito mais.

uint8_t* byte_p = (uint8_t*)p;
for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
  printf("%x ",*i);
}
Winfield Chen
fonte
0

Você pode imprimir facilmente uma impressora vazia

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));
Ankit Bansal
fonte
1
Quem garante que voidtem o mesmo tamanho que int?
Ant_222
Tecnicamente void, isso não está imprimindo um ponteiro, mas sim intno endereço de memória que o ponteiro contém (que nesse caso seria o valor de p).
Marionumber1
0

Como C é uma linguagem de tipo estatístico e de tipo forte, você deve decidir o tipo de variável antes da compilação. Ao tentar emular genéricos em C, você tentará reescrever o C ++ novamente, portanto, seria melhor usar o C ++.

adem
fonte
0

ponteiro nulo é um ponteiro genérico. Endereço de qualquer tipo de dados de qualquer variável pode ser atribuído a um ponteiro nulo.

int a = 10;
float b = 3.14;
void *ptr;
ptr = &a;
printf( "data is %d " , *((int *)ptr)); 
//(int *)ptr used for typecasting dereferencing as int
ptr = &b;
printf( "data is %f " , *((float *)ptr));
//(float *)ptr used for typecasting dereferencing as float
N.Annamalai Raj
fonte
0

Você não pode desreferenciar um ponteiro sem especificar seu tipo, porque tipos de dados diferentes terão tamanhos diferentes na memória, ou seja, um int com 4 bytes, um char com 1 byte.

gotoat
fonte
-1

Fundamentalmente, em C, "tipos" são uma maneira de interpretar bytes na memória. Por exemplo, qual o código a seguir

struct Point {
  int x;
  int y;
};

int main() {
  struct Point p;
  p.x = 0;
  p.y = 0;
}

Diz "Quando executo main, quero alocar 4 (tamanho do número inteiro) + 4 (tamanho do número inteiro) = 8 (total de bytes) de memória. Quando escrevo '.x' como um lvalue em um valor com o rótulo de tipo Aponte no tempo de compilação, recupere dados do local da memória do ponteiro e mais quatro bytes.Dê ao valor de retorno o rótulo de tempo de compilação "int".

Dentro do computador em tempo de execução, sua estrutura "Ponto" é assim:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

E aqui está a void*aparência do seu tipo de dados: (assumindo um computador de 32 bits)

10001010 11111001 00010010 11000101
noɥʇʎԀʎzɐɹƆ
fonte
Isso não responder à pergunta
MM
-2

Isso não funcionará, mas o void * pode ajudar muito na definição de ponteiro genérico para funções e transmiti-lo como argumento para outra função (semelhante ao retorno de chamada em Java) ou definir uma estrutura semelhante ao oop.

Rami
fonte