Concepto de puntero vacío en programación C

129

¿Es posible desreferenciar un puntero vacío sin conversión de tipos en el lenguaje de programación C?

Además, ¿hay alguna forma de generalizar una función que pueda recibir un puntero y almacenarlo en un puntero vacío y, al usar ese puntero vacío, podemos hacer una función generalizada?

por ejemplo:

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
}

Quiero hacer que esta función sea genérica sin usar sentencias if-else: ¿es esto posible?

Además, si hay buenos artículos en Internet que explican el concepto de un puntero nulo, sería beneficioso si pudiera proporcionar las URL.

Además, ¿es posible la aritmética de puntero con punteros vacíos?

Un geek
fuente

Respuestas:

97

¿Es posible desreferenciar el puntero vacío sin conversión de tipos en lenguaje de programación C ...

No, voidindica la ausencia de tipo, no es algo a lo que se pueda desreferenciar o asignar.

si hay alguna forma de generalizar una función que pueda recibir el puntero y almacenarlo en un puntero vacío y al usar ese puntero vacío podemos hacer una función generalizada.

No puede simplemente desreferenciarlo de forma portátil, ya que puede que no esté alineado correctamente. Puede ser un problema en algunas arquitecturas como ARM, donde el puntero a un tipo de datos debe estar alineado en el límite del tamaño del tipo de datos (por ejemplo, el puntero al entero de 32 bits debe estar alineado en el límite de 4 bytes para ser desreferenciado).

Por ejemplo, leyendo uint16_tde 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);

Además, es posible la aritmética del puntero con punteros vacíos ...

La aritmética del puntero no es posible en punteros voiddebido a la falta de valor concreto debajo del puntero y, por lo tanto, del tamaño.

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */
Alex B
fuente
2
A menos que recuerde incorrectamente, puede desreferenciar de forma segura un vacío * de dos maneras. El envío a char * siempre es aceptable, y si conoce el tipo original al que apunta, puede enviar a ese tipo. El vacío * ha perdido la información de tipo, por lo que debería almacenarse en otro lugar.
Dan Olson
1
Sí, siempre puedes desreferenciar si lanzas a otro tipo, pero no puedes hacer lo mismo si no lanzas. En resumen, el compilador no sabrá qué instrucciones de ensamblaje usar para las operaciones matemáticas hasta que lo lance.
Zachary Hamm
20
Creo que GCC trata la aritmética en void *punteros de la misma manera que char *, pero no es estándar y no debes confiar en ella.
ephemient
55
No estoy seguro de seguirlo. Por ejemplo, el usuario de la función garantiza que el OP de la lista anterior indica que los tipos entrantes son correctos. ¿Está diciendo que si tenemos algo donde asignamos un valor de tipo conocido a un puntero, lo convertimos en un puntero vacío y luego lo devolvemos al tipo de puntero original que podría quedar desalineado? No entiendo por qué el compilador lo socavaría de esa manera simplemente desalineando las cosas simplemente porque las arroja a un puntero vacío. ¿O simplemente está diciendo que no podemos confiar en que el usuario sepa el tipo de argumentos que transmitió a la función?
doliver
2
@doliver quise decir # 2 ("no podemos confiar en que el usuario sepa el tipo de argumentos que le pasó a la función"). Pero volviendo a mi respuesta de hace 6 años (uf, ¿realmente ha pasado tanto tiempo?) Puedo ver lo que quieres decir, no siempre es así: cuando el puntero original señalaba el tipo correcto, ya estaría alineado, por lo que Sería seguro lanzar sin problemas de alineación.
Alex B
31

En C, a void *se puede convertir en un puntero a un objeto de un tipo diferente sin una conversión explícita:

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

Sin embargo, esto no ayuda a escribir su función de una manera más genérica.

No puede desreferenciar a void *con convertirlo a un tipo de puntero diferente ya que al desreferenciar un puntero se obtiene el valor del objeto señalado. Un desnudo voidno es un tipo válido, por void *lo que no es posible desreferenciarlo .

La aritmética del puntero se trata de cambiar los valores del puntero por múltiplos de los sizeofobjetos apuntados. Nuevamente, debido a voidque no es un tipo verdadero, sizeof(void)no tiene significado, por lo que la aritmética del puntero no es válida void *. (Algunas implementaciones lo permiten, utilizando la aritmética de puntero equivalente para char *).

CB Bailey
fuente
1
@CharlesBailey En tu código, escribes void abc(void *a, int b). Pero por qué no usarlo void abc(int *a, int b), ya que en su función eventualmente definirá int *test = a;, guardaría una variable, y no veo ninguna otra ventaja al definir otra variable. Veo muchos códigos escritos de esta manera usando void *como parámetros y enviando variables más adelante en la función. Pero dado que esta función está escrita por el autor, debe saber el uso de la variable, por lo tanto, ¿por qué usarla void *? Gracias
Lion Lai
15

Debe tener en cuenta que en C, a diferencia de Java o C #, no hay absolutamente ninguna posibilidad de "adivinar" con éxito el tipo de objeto al que void*apunta un puntero. Algo similar a getClass()simplemente no existe, ya que esta información no se encuentra en ninguna parte. Por esa razón, el tipo de "genérico" que busca siempre viene con metainformación explícita, como int ben su ejemplo o la cadena de formato en la printffamilia de funciones.

Erich Kitzmueller
fuente
7

Un puntero vacío se conoce como puntero genérico, que puede referirse a variables de cualquier tipo de datos.

Sneha
fuente
6

Hasta ahora, mi comprensión del puntero vacío es la siguiente.

Cuando se declara una variable de puntero utilizando la palabra clave void, se convierte en una variable de puntero de uso general. La dirección de cualquier variable de cualquier tipo de datos (char, int, float, etc.) se puede asignar a una variable de puntero vacío.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Dado que se puede asignar otro puntero de tipo de datos al puntero vacío, entonces lo usé en la función absolut_value (el código se muestra a continuación). Para hacer una función general.

Traté de escribir un código C simple que toma un entero o flotante como argumento y trata de hacerlo + ve, si es negativo. Escribí el siguiente 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;
}   

Pero recibía un error, así que llegué a saber que mi comprensión con el puntero de vacío no es correcta :(. Así que ahora me moveré hacia la recopilación de puntos, ¿por qué es así?

Lo que necesito entender más sobre los punteros nulos es eso.

Necesitamos encasillar la variable de puntero vacío para desreferenciarla. Esto se debe a que un puntero vacío no tiene ningún tipo de datos asociado. No hay forma de que el compilador pueda saber (¿o adivinar?) A qué tipo de datos apunta el puntero vacío. Por lo tanto, para tomar los datos señalados por un puntero vacío, los encasillamos con el tipo correcto de datos retenidos dentro de la ubicación de los punteros vacíos.

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) );

}

Un puntero nulo puede ser realmente útil si el programador no está seguro sobre el tipo de datos ingresados ​​por el usuario final. En tal caso, el programador puede usar un puntero vacío para apuntar a la ubicación del tipo de datos desconocido. El programa se puede configurar de tal manera que solicite al usuario que informe el tipo de datos y la conversión de tipos se puede realizar de acuerdo con la información ingresada por el usuario. A continuación se incluye un fragmento de código.

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
}

Otro punto importante que debe tener en cuenta sobre los punteros vacíos es que: la aritmética del puntero no se puede realizar en un puntero vacío.

void *ptr;

int a;

ptr=&a;

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

Así que ahora entendí cuál fue mi error. Estoy corrigiendo lo mismo.

Referencias

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

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

El nuevo código es como se muestra a continuación.


#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;
}   

Gracias,

Megharaj
fuente
2

No, no es posible. ¿Qué tipo debe tener el valor desreferenciado?

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

fuente
Este programa es posible .... Por favor, compruebe, si esto es posible en la programación en C ...
AGeek
2
Sí, esto es posible (aunque el código es peligroso sin una verificación de rango para b). Printf toma un número variable de argumentos y a simplemente se inserta en la pila como cualquier puntero (sin ningún tipo de información) y aparece dentro de la función printf usando macros va_arg usando información de la cadena de formato.
hlovdal
@SiegeX: describa lo que no es portátil y lo que puede no estar definido.
Gauthier
@Gauthier pasar una variable que contiene especificadores de formato no es portátil y su comportamiento es potencialmente indefinido. Si quieres hacer algo así, mira el sabor 'vs' de printf. Esta respuesta tiene un buen ejemplo para eso.
SiegeX
En la práctica, probablemente lo reemplazaría int bcon una enumeración, pero hacer cualquier cambio posterior a esa enumeración rompería esta función.
Jack Stout
1

Quiero hacer que esta función sea genérica, sin usar ifs; ¿Es posible?

La única forma simple que veo es utilizar la sobrecarga ... que no está disponible en el lenguaje de programación C AFAIK.

¿Consideró el lenguaje de programación C ++ para su programa? ¿O hay alguna restricción que prohíba su uso?

yves Baumes
fuente
Ok, es una pena. Desde mi punto de vista, no veo soluciones entonces. En realidad, es lo mismo que printf () & cout: 2 formas diferentes de implementar la impresión. printf () usa la instrucción if () para decodificar la cadena de formato (supongo o algo similar), mientras que para cout case operator << es una función sobrecargada
yves Baumes
1

Aquí hay un breve puntero sobre voidpunteros: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Punteros vacíos

Debido a que el puntero vacío no sabe a qué tipo de objeto está apuntando, ¡no puede ser desreferenciado directamente! Por el contrario, el puntero nulo primero debe convertirse explícitamente a otro tipo de puntero antes de desreferenciarse.

Si un puntero vacío no sabe a qué apunta, ¿cómo sabemos a qué lanzarlo? En última instancia, eso depende de usted para realizar un seguimiento.

Miscelánea de puntero vacío

No es posible hacer aritmética de puntero en un puntero vacío. Esto se debe a que la aritmética del puntero requiere que el puntero sepa a qué tamaño de objeto está apuntando, por lo que puede aumentar o disminuir el puntero adecuadamente.

Suponiendo que la memoria de la máquina es direccionable por bytes y no requiere accesos alineados, la forma más genérica y atómica (más cercana a la representación a nivel de máquina) de interpretar a void*es como un puntero a un byte uint8_t*. Lanzar un void*a a uint8_t*le permitiría, por ejemplo, imprimir los primeros 1/2/4/8 / sin embargo, muchos bytes que desee a partir de esa dirección, pero no puede hacer mucho más.

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

Puede imprimir fácilmente una impresora vacía

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));
Ankit Bansal
fuente
1
¿Quién garantiza que voidtiene el mismo tamaño que int?
Ant_222
Esto no es técnicamente imprimir un voidpuntero, está imprimiendo inten la dirección de memoria que contiene el puntero (que en este caso sería el valor de p).
Marionumber1
0

Debido a que C es un lenguaje fuertemente tipado estáticamente, debe decidir el tipo de variable antes de compilar. Cuando intentes emular genéricos en C, terminarás intentando reescribir C ++ nuevamente, por lo que sería mejor usar C ++ en su lugar.

ademas
fuente
0

El puntero vacío es un puntero genérico. La dirección de cualquier tipo de datos de cualquier variable puede asignarse a un puntero vacío.

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
fuente
0

No puede desreferenciar un puntero sin especificar su tipo porque los diferentes tipos de datos tendrán diferentes tamaños en la memoria, es decir, un int es de 4 bytes, un char es de 1 byte.

gotoat
fuente
-1

Básicamente, en C, los "tipos" son una forma de interpretar los bytes en la memoria. Por ejemplo, cuál es el siguiente código

struct Point {
  int x;
  int y;
};

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

Dice "Cuando ejecuto main, quiero asignar 4 (tamaño de entero) + 4 (tamaño de entero) = 8 (bytes totales) de memoria. Cuando escribo '.x' como valor de l en un valor con la etiqueta de tipo Apunte al tiempo de compilación, recupere datos de la ubicación de la memoria del puntero más cuatro bytes. Dé al valor de retorno la etiqueta de tiempo de compilación "int".

Dentro de la computadora en tiempo de ejecución, su estructura de "Punto" se ve así:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Y así es void*como se vería su tipo de datos: (suponiendo que una computadora de 32 bits)

10001010 11111001 00010010 11000101
noɥʇʎԀʎzɐɹƆ
fuente
Esto no responde la pregunta
MM
-2

Esto no funcionará, pero void * puede ayudar mucho a definir el puntero genérico a las funciones y pasarlo como argumento a otra función (similar a la devolución de llamada en Java) o definirlo como una estructura similar a oop.

Rami
fuente