2010-08-30 13 views
17

$ 5.7 -puntero Además vs resta

"[..] Por otra parte, ya sea ambos operadores tendrán la aritmética o tipo de enumeración, o un operando será un puntero a un tipo de objeto definido por completo y por otro, tendrán integrante . o enumeración tipo

2 para la resta, una de las siguientes asirá: - ambos operandos tienen aritmética o tipo de enumeración; o - ambos operandos son punteros a versiones cv-calificados o CV-no calificado de la misma completamente definidos tipo de objeto; o - el operando de la izquierda es un puntero a un tipo de objeto completamente definido y el operando de la derecha tiene i ntegral o tipo de enumeración.

int main(){ 
     int buf[10]; 
     int *p1 = &buf[0]; 
     int *p2 = 0; 

     p1 + p2;  // Error 

     p1 - p2;  // OK 
} 

Por lo tanto, mi pregunta es ¿por qué 'Además puntero' no es compatible en C++, pero 'puntero de la resta' es?

+3

Nota: la adición del puntero es compatible en el sentido de que 'T * p =/** /; p = p + 1' es válido (y apunta un elemento más).Sin embargo, difiere un poco de tu pregunta :) –

+0

muchas respuestas indican que la diferencia significaría el número de objetos de un tipo que se puede almacenar entre dos punteros p1 y p2. ¿Qué sucede si el puntero es del tipo 'void *'? – Chubsdad

+0

@chubsdad: entonces no puedes restarlos. – Potatoswatter

Respuesta

27

La diferencia entre dos punteros significa la cantidad de elementos del tipo que cabría entre los objetivos de los dos punteros. La suma de dos punteros significa ... er ... nada, por lo que no es compatible.

18

El resultado de la resta es la distancia (útil).

El resultado de agregar un puntero y una distancia es otro puntero significativo.

El resultado de agregar 2 punteros es otro puntero, esta vez sin sentido.

Es la misma razón por la que hay distintos objetos TimeSpan y DateTime en la mayoría de las bibliotecas.

+0

+1 para una gran analogía fecha/hora – mk12

3

El resultado de la resta del puntero es el número de objetos entre dos direcciones de memoria. La adición del puntero no significa nada, es por eso que no está permitido.

4

Lo primero que viene a la mente es que no tiene sentido hacer la adición del puntero, por lo que no es compatible. Si tiene 2 punteros 0x45ff23dd, 0x45ff23ed. ¿Qué significa agregarlos? Algunos recuerdos fuera de límites. Y las personas en el comité estándar no han encontrado motivos suficientemente buenos para admitir cosas como esa, y más bien lo advierten en el momento de la compilación sobre un posible problema. Mientras que la resta del puntero está bien porque indica la distancia de la memoria, que a menudo es útil.

2

Porque agregar dos punteros no tiene sentido.

Considere que tengo dos int s en memoria en 0x1234 y 0x1240. La diferencia entre estas direcciones es 0xc y es una distancia en la memoria. La suma es 0x2474 y no corresponde a nada significativo.

Usted puede sin embargo, agregue un puntero a un número entero para obtener otro puntero. Esto es lo que haces cuando indexas en una matriz: p [4] significa * (p + 4) que significa "la cosa almacenada en la dirección 4 unidades más allá de esta dirección".

En general, puede determinar la "puntería" de una operación aritmética asignando a cada puntero un valor 1 y cada número entero un valor cero. Si el resultado es 1, tienes un puntero; si es 0, tienes un número entero; si tiene algún otro valor, tienes algo que no tiene sentido.Ejemplos:

/* here p,q,r are pointers, i,j,k are integers */ 
p + i; /* 1 + 0 == 1 => p+i is a pointer */ 
p - q; /* 1 - 1 == 0 => p-q is an integer */ 
p + (q-r); /* 1 + (1-1) == 1 => pointer */ 
0

Porque el resultado de esa operación no está definido. ¿A dónde apunta p1 + p2? ¿Cómo puede asegurarse de que apunta a una memoria correctamente inicializada para que pueda ser eliminada posteriormente? p1 - p2 da el desplazamiento entre esos 2 punteros y ese resultado podría usarse más adelante.

0

resta de punteros solo se define si apuntan a la misma matriz de objetos. La resta resultante se escala según el tamaño del objeto al que apuntan. es decir, la resta del puntero da la cantidad de elementos entre los dos punteros.

1

N.B. No hay reclamos sobre los estándares C aquí.

Como una adición rápida a la respuesta de @Brian Hooper, "[t] la suma de dos punteros significa ... er ... nada", sin embargo, la suma de un puntero y un entero permite compensar desde el puntero inicial .

Restar un puntero de mayor valor desde un puntero de valor inferior le proporciona el desplazamiento entre los dos. Tenga en cuenta que no estoy contabilizando la búsqueda de memoria aquí; Supongo que los valores de memoria están dentro del alcance accesible del proceso.

Así que si tiene un puntero a una serie de ubicaciones de memoria consecutivas en el montón, o una matriz de ubicaciones de memoria en la pila (cuyo nombre de variable decae a un puntero), estos punteros (el puntero real y el que se desintegra a un puntero) apuntará a la pregunta de la ubicación de la memoria del puño (es decir, el elemento [0]). Agregar un valor entero al puntero equivale a incrementar el índice entre paréntesis por el mismo número.

#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
    // This first declaration does several things (this is conceptual and not an exact list of steps the computer takes): 
    //  1) allots space on the stack for a variable of type pointer 
    //  2) allocates number of bytes on the heap necessary to fit number of chars in initialisation string 
    //   plus the NULL termination '\0' (i.e. sizeof(char) * <characters in string> + 1 for '\0') 
    //  3) changes the value of the variable from step 1 to the memory address of the beginning of the memory 
    //   allocated in step 2 
    // The variable iPointToAMemoryLocationOnTheHeap points to the first address location of the memory that was allocated. 
    char *iPointToAMemoryLocationOnTheHeap = "ABCDE"; 

    // This second declaration does the following: 
    //  1) allots space on the stack for a variable that is not a pointer but is said to decay to a pointer allowing 
    //   us to so the following iJustPointToMemoryLocationsYouTellMeToPointTo = iPointToAMemoryLocationOnTheHeap; 
    //  2) allots number of bytes on the stack necessary to fit number of chars in initialisation string 
    //   plus the NULL termination '\0' (i.e. sizeof(char) * <characters in string> + 1 for '\0') 
    // The variable iPointToACharOnTheHeap just points to first address location. 
    // It just so happens that others follow which is why null termination is important in a series of chars you treat 
    char iAmASeriesOfConsecutiveCharsOnTheStack[] = "ABCDE"; 

    // In both these cases it just so happens that other chars follow which is why null termination is important in a series 
    // of chars you treat as though they are a string (which they are not). 

    char *iJustPointToMemoryLocationsYouTellMeToPointTo = NULL; 

    iJustPointToMemoryLocationsYouTellMeToPointTo = iPointToAMemoryLocationOnTheHeap; 

    // If you increment iPointToAMemoryLocationOnTheHeap, you'll lose track of where you started 
    for(; *(++iJustPointToMemoryLocationsYouTellMeToPointTo) != '\0' ;) { 
     printf("Offset of: %ld\n", iJustPointToMemoryLocationsYouTellMeToPointTo - iPointToAMemoryLocationOnTheHeap); 
     printf("%s\n", iJustPointToMemoryLocationsYouTellMeToPointTo); 
     printf("%c\n", *iJustPointToMemoryLocationsYouTellMeToPointTo); 
    } 

    printf("\n"); 

    iJustPointToMemoryLocationsYouTellMeToPointTo = iPointToAMemoryLocationOnTheHeap; 

    for(int i = 0 ; *(iJustPointToMemoryLocationsYouTellMeToPointTo + i) != '\0' ; i++) { 
     printf("Offset of: %ld\n", (iJustPointToMemoryLocationsYouTellMeToPointTo + i) - iPointToAMemoryLocationOnTheHeap); 
     printf("%s\n", iJustPointToMemoryLocationsYouTellMeToPointTo + i); 
     printf("%c\n", *iJustPointToMemoryLocationsYouTellMeToPointTo + i); 
    } 

    printf("\n"); 

    iJustPointToMemoryLocationsYouTellMeToPointTo = iAmASeriesOfConsecutiveCharsOnTheStack; 

    // If you increment iAmASeriesOfConsecutiveCharsOnTheStack, you'll lose track of where you started 
    for(; *(++iJustPointToMemoryLocationsYouTellMeToPointTo) != '\0' ;) { 
     printf("Offset of: %ld\n", iJustPointToMemoryLocationsYouTellMeToPointTo - iAmASeriesOfConsecutiveCharsOnTheStack); 
     printf("%s\n", iJustPointToMemoryLocationsYouTellMeToPointTo); 
     printf("%c\n", *iJustPointToMemoryLocationsYouTellMeToPointTo); 
    } 

    printf("\n"); 

    iJustPointToMemoryLocationsYouTellMeToPointTo = iAmASeriesOfConsecutiveCharsOnTheStack; 

    for(int i = 0 ; *(iJustPointToMemoryLocationsYouTellMeToPointTo + i) != '\0' ; i++) { 
     printf("Offset of: %ld\n", (iJustPointToMemoryLocationsYouTellMeToPointTo + i) - iAmASeriesOfConsecutiveCharsOnTheStack); 
     printf("%s\n", iJustPointToMemoryLocationsYouTellMeToPointTo + i); 
     printf("%c\n", *iJustPointToMemoryLocationsYouTellMeToPointTo + i); 
    } 
    return 1; 
} 

Lo primero destacable que hacemos en este programa es copiar el valor del puntero iPointToAMemoryLocationOnTheHeap-iJustPointToMemoryLocationsYouTellMeToPointTo. Así que ahora ambos apuntan a la misma ubicación de memoria en el montón. Hacemos esto para no perder la pista del comienzo.

En el primer bucle for, incrementamos el valor que acabamos de copiar en iJustPointToMemoryLocationsYouTellMeToPointTo (incrementándolo en 1 significa que apunta a una ubicación de memoria más alejada de iPointToAMemoryLocationOnTheHeap).

El segundo ciclo es similar pero quería mostrar más claramente cómo el aumento del valor se relaciona con el desplazamiento y cómo funciona la aritmética.

El tercer y cuarto bucle repiten el proceso pero trabajan en una matriz en la pila en lugar de la memoria asignada en el montón.

Tenga en cuenta el asterisco * al imprimir el char individual. Esto le dice a printf que muestre lo que apunta la variable, y no el contenido de la variable en sí. Esto está en contraste con la línea de arriba donde se imprime el resto de la cadena y no hay asterisco antes de la variable porque printf() está mirando la serie de ubicaciones de memoria en su totalidad hasta que se alcanza NULL.

Aquí está la salida en ubuntu 15.10 ejecutándose en un i7 (el primer y tercer bucle de salida comienzan en un desplazamiento de 1 porque mi elección de bucle de for se incrementa al principio del bucle en vez de do{}while(); solo quería para mantenerlo simple):

Offset of: 1 
BCDE 
B 
Offset of: 2 
CDE 
C 
Offset of: 3 
DE 
D 
Offset of: 4 
E 
E 

Offset of: 0 
ABCDE 
A 
Offset of: 1 
BCDE 
B 
Offset of: 2 
CDE 
C 
Offset of: 3 
DE 
D 
Offset of: 4 
E 
E 

Offset of: 1 
BCDE 
B 
Offset of: 2 
CDE 
C 
Offset of: 3 
DE 
D 
Offset of: 4 
E 
E 

Offset of: 0 
ABCDE 
A 
Offset of: 1 
BCDE 
B 
Offset of: 2 
CDE 
C 
Offset of: 3 
DE 
D 
Offset of: 4 
E 
E