Aritmética de puntero para puntero vacío en C

176

Cuando un puntero a un tipo particular (por ejemplo int, char, float, ..) se incrementa, su valor se incrementa el tamaño de ese tipo de datos. Si se incrementa un voidpuntero que apunta a datos de tamaño x, ¿cómo llega a apuntar xbytes por delante? ¿Cómo sabe el compilador agregar xvalor al puntero?

Siva Sankaran
fuente
posible duplicado de Aritmética de puntero
Carl Norum
3
La pregunta parece que supone que el compilador (/ tiempo de ejecución) sabe en qué tipo de objeto se configuró el puntero y agrega su tamaño al puntero. Esa es una idea falsa completa: solo conoce la dirección.
PJTraill
2
"Si se incrementa un voidpuntero que apunta a datos de tamaño x, ¿cómo llega a apuntar xbytes por delante?" No lo hace. ¿Por qué las personas que tienen esas preguntas no pueden evaluarlas antes de preguntar? Ya saben, al menos al mínimo, donde verifican si realmente se compila, lo cual no es así. -1, no puedo creer que esto haya obtenido +100 y -0.
underscore_d

Respuestas:

287

Conclusión final: la aritmética en a void*es ilegal tanto en C como en C ++.

GCC lo permite como una extensión, consulte Aritmética voidactivada y Punteros de función (tenga en cuenta que esta sección es parte del capítulo "Extensiones C" del manual). Clang e ICC probablemente permitan la void*aritmética para fines de compatibilidad con GCC. Otros compiladores (como MSVC) no permiten la aritmética activada void*, y GCC no lo permite si -pedantic-errorsse especifica el indicador, o si -Werror-pointer-arithse especifica el indicador (este indicador es útil si su base de código también debe compilar con MSVC).

El estándar C habla

Las citas están tomadas del borrador n1256.

La descripción del estándar de la operación de adición establece:

6.5.6-2: Además, ambos operandos deben tener un tipo aritmético o un operando debe ser un puntero a un tipo de objeto y el otro debe tener un tipo entero.

Entonces, la pregunta aquí es si void*es un puntero a un "tipo de objeto", o de manera equivalente, si voides un "tipo de objeto". La definición de "tipo de objeto" es:

6.2.5.1: Los tipos se dividen en tipos de objeto (tipos que describen completamente los objetos), tipos de función (tipos que describen funciones) y tipos incompletos (tipos que describen objetos pero carecen de la información necesaria para determinar sus tamaños).

Y el estándar define voidcomo:

6.2.5-19: El voidtipo comprende un conjunto vacío de valores; Es un tipo incompleto que no se puede completar.

Como voides un tipo incompleto, no es un tipo de objeto. Por lo tanto, no es un operando válido para una operación de suma.

Por lo tanto, no puede realizar aritmética de puntero en un voidpuntero.

Notas

Originalmente, se pensaba que la void*aritmética estaba permitida, debido a estas secciones del estándar C:

6.2.5-27: Un puntero a anular tendrá los mismos requisitos de representación y alineación que un puntero a un tipo de carácter.

Sin embargo,

Los mismos requisitos de representación y alineación están destinados a implicar intercambiabilidad como argumentos de funciones, valores de retorno de funciones y miembros de sindicatos.

Esto significa que printf("%s", x)tiene el mismo significado si xtiene tipo char*o void*, pero no significa que pueda hacer aritmética en a void*.

Nota del editor: esta respuesta ha sido editada para reflejar la conclusión final.

Sadeq
fuente
77
Del estándar C99: (6.5.6.2) Además, ambos operandos deben tener un tipo aritmético o un operando debe ser un puntero a un tipo de objeto y el otro debe tener un tipo entero. (6.2.5.19) El tipo de vacío comprende un conjunto vacío de valores; Es un tipo incompleto que no se puede completar. Creo que eso deja en claro que la void*aritmética de punteros no está permitida. GCC tiene una extensión que permite hacer esto.
Trabajo
1
Si ya no crees que tu respuesta es útil, puedes eliminarla.
caf
1
Esta respuesta fue útil a pesar de que resultó incorrecta, ya que contiene pruebas concluyentes de que los punteros nulos no están destinados a la aritmética.
Ben Flynn
1
Esta es una buena respuesta, tiene la conclusión correcta y las citas necesarias, pero las personas que llegaron a esta pregunta llegaron a una conclusión incorrecta porque no leyeron al final de la respuesta. He editado esto para hacerlo más obvio.
Dietrich Epp
1
Clang e ICC no permiten la void*aritmética (al menos por defecto).
Sergey Podobry
61

La aritmética de puntero no está permitida en void*punteros.

Trabajo
fuente
16
La aritmética de puntero +1 solo se define para punteros a tipos de objetos (completos) . voides un tipo incompleto que nunca se puede completar por definición.
schot
1
@schot: Exactamente. Además, la aritmética del puntero solo se define en un puntero a un elemento de un objeto de matriz y solo si el resultado de la operación sería un puntero a un elemento en esa misma matriz o uno más allá del último elemento de esa matriz. Si no se cumplen esas condiciones, es un comportamiento indefinido. (Del estándar C99 6.5.6.8)
Trabajo
1
Aparentemente no es así con gcc 7.3.0. El compilador acepta p + 1024, donde p es nulo *. Y el resultado es el mismo que ((char *) p) + 1024
zzz777
@ zzz777 es una extensión de GCC, vea el enlace en la respuesta más votada.
Ruslan
19

póngalo en un puntero char e incremente su puntero adelante x bytes adelante.

Gobblement supremo
fuente
44
¿Por qué molestarse? Simplemente tírelo al tipo correcto, que siempre debe ser conocido, e increméntelo en 1. La conversión char, el incremento xy la reinterpretación del nuevo valor como algún otro tipo es un comportamiento inútil e indefinido.
underscore_d
9
Si está escribiendo su función de clasificación, que según man 3 qsortdebería tener la void qsort(void *base, size_t nmemb, size_t size, [snip]), entonces no tiene forma de conocer el "tipo correcto"
alisianoi
15

El estándar C no permite la aritmética de puntero vacío . Sin embargo, GNU C está permitido considerando el tamaño de vacío es decir 1.

Norma C11 §6.2.5

Párrafo - 19

El voidtipo comprende un conjunto de valores vacío; Es un tipo de objeto incompleto que no se puede completar.

El siguiente programa funciona bien en el compilador GCC.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Puede haber otros compiladores que generen un error.

msc
fuente
14

No puede hacer aritmética de puntero en void *tipos, ¡exactamente por esta razón!

Oliver Charlesworth
fuente
8

Debe lanzarlo a otro tipo de puntero antes de hacer la aritmética del puntero.

Jakob
fuente
7

Los punteros vacíos pueden apuntar a cualquier fragmento de memoria. Por lo tanto, el compilador no sabe cuántos bytes aumentar / disminuir cuando intentamos la aritmética del puntero en un puntero vacío. Por lo tanto, los punteros vacíos deben primero ser encasillados a un tipo conocido antes de que puedan participar en cualquier aritmética de puntero.

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.
tchrist
fuente
-1

Compilador sabe por tipo de reparto. Dado un void *x:

  • x+1agrega un byte a x, el puntero va al bytex+1
  • (int*)x+1agrega sizeof(int)bytes, el puntero va al bytex + sizeof(int)
  • (float*)x+1direcciones sizeof(float)bytes, etc.

Aunque el primer elemento no es portátil y está en contra del Galateo de C / C ++, sin embargo, es correcto para el lenguaje C, lo que significa que se compilará en algo en la mayoría de los compiladores que posiblemente necesiten un indicador apropiado (como -Wpointer-arith)

rytis
fuente
2
Althought the first item is not portable and is against the Galateo of C/C++Cierto. it is nevertheless C-language-correctFalso. ¡Esto es doble pensamiento! La aritmética del puntero void *es sintácticamente ilegal, no debe compilarse y produce un comportamiento indefinido si lo hace. Si un programador descuidado puede compilarlo deshabilitando alguna advertencia, eso no es excusa.
underscore_d
@underscore_d: Creo que algunos compiladores solían permitirlo como una extensión, ya que es mucho más conveniente que tener que convertirlo, unsigned char*por ejemplo, para agregar un sizeofvalor a un puntero.
supercat