2010-08-30 5 views
17

$ 5,7 -Pointer сложения против вычитания

«[..] Для того, либо оба операнда имеет арифметический или тип перечисления, или один из операндов должны быть указателем на совершенно определенный тип объекта, а другие имеет интеграл . или перечисление типа

2 Для вычитания, один из следующих проводит: - оба операнда имеют арифметические или тип перечисления, или - оба операнда являются указателями на резюме квалифицированных или CV-неквалифицированных версий одного и того же полностью определяется тип объекта или - левый операнд является указателем на полностью определенный тип объекта, а правый операнд имеет i ntegral или перечисление.

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

     p1 + p2;  // Error 

     p1 - p2;  // OK 
} 

Итак, мой вопрос, почему «указатель дополнение» не поддерживается в C++, но «указатель Вычитание» есть?

+3

Примечание: добавление указателя поддерживается в том смысле, что 'T * p =/** /; p = p + 1' (и указывает еще один элемент).Это немного отличается от вашего вопроса, хотя :) –

+0

Многие ответы указывают на то, что разница будет означать количество объектов типа, которые могут быть сохранены между двумя указателями p1 и p2. Что, если указатель имеет тип «void *»? – Chubsdad

+0

@chubsdad: тогда вы не можете их вычесть. – Potatoswatter

ответ

27

Разница между двумя указателями означает количество элементов типа, которые бы соответствовали между целями двух указателей. Сумма двух указателей означает ... er ... ничего, поэтому она не поддерживается.

18

Результат вычитания - это расстояние (полезно).

Результатом добавления указателя и расстояния является другой значащий указатель.

Результат добавления 2 указателей - другой указатель, на этот раз бессмысленный.

По той же причине в большинстве библиотек существуют различные объекты TimeSpan и DateTime.

+0

+1 для отличной аналоговой даты/времени – mk12

3

Результат вычитания указателя - это количество объектов между двумя адресами памяти. Добавление указателя ничего не значит, поэтому это недопустимо.

4

Первое, что приходит на ум, - это не имеет смысла делать добавление указателя, поэтому оно не поддерживается. Если у вас есть 2 указателя 0x45ff23dd, 0x45ff23ed. Что значит добавить их? Некоторая память вне пределов. И люди в стандартном комитете не нашли достаточных оснований для поддержки подобных вещей и скорее предупреждают вас о времени компиляции о возможных проблемах. В то время как вычитание указателя отлично, потому что оно указывает расстояние до памяти, что часто полезно.

2

Потому что добавление двух указателей не имеет смысла.

У меня есть два int s в памяти по адресу 0x1234 и 0x1240. Разница между этими адресами составляет 0xc и является расстоянии в памяти. Сумма равна 0x2474 и не соответствует чему-либо значимому.

Вы носите , однако добавьте указатель на целое число, чтобы получить другой указатель. Это то, что вы делаете при индексировании в массив: p [4] означает * (p + 4), что означает «вещь, хранящаяся по адресу 4 единицы за этим адресом».

В общем, вы можете определить «pointerness» арифметической операции, назначив каждому указателю значение 1 и каждое целое число равным нулю. Если результат равен 1, у вас есть указатель; если это 0, у вас есть целое число; если это какая-то другая ценность, у вас есть что-то, что не имеет смысла.Примеры:

/* 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

Поскольку результат этой операции не определен. Где находится p1 + p2? Как вы можете убедиться, что он указывает на правильно инициализированную память, чтобы ее можно было разыменовать позже? p1 - p2 дает смещение между этими двумя указателями, и этот результат может быть использован далее.

0

вычитание указателей определяется только в том случае, если они указывают на один и тот же массив объектов. Полученное вычитание масштабируется по размеру объекта, на который они указывают. т.е. вычитание указателя дает количество элементов между двумя указателями.

1

N.B. Никаких претензий на стандарты С здесь нет.

В качестве быстрого добавления к ответу @Brian Hooper: «[t] сумма двух указателей означает ... er ... ничего», однако сумма указателя и целого числа позволяет вам компенсировать исходный указатель ,

Вычитание указателя более высокого значения из указателя с более низким значением дает вам смещение между ними. Обратите внимание, что я не учитываю пейджинг памяти здесь; Я предполагаю, что значения памяти находятся в пределах доступной области процесса.

Таким образом, если у вас есть указатель на ряд последовательных мест памяти в куче или массив ячеек памяти в стеке (имя переменной которого распадается на указатель), эти указатели (реальный указатель и тот, который распадается на указатель) укажет на вопрос о местоположении ячейки кулака (т. е. элемент [0]). Добавление целочисленного значения в указатель эквивалентно увеличению индекса в скобках на тот же номер.

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

Первое заметное, что мы делаем в этой программе скопировать значение указателя iPointToAMemoryLocationOnTheHeap к iJustPointToMemoryLocationsYouTellMeToPointTo. Итак, теперь оба они указывают на то же место памяти в куче. Мы делаем это, чтобы не потерять из виду его начало.

В первом цикле for мы увеличиваем значение, которое мы только что скопировали в iJustPointToMemoryLocationsYouTellMeToPointTo (увеличение на 1 означает, что оно указывает на одно место памяти, расположенное дальше от iPointToAMemoryLocationOnTheHeap).

Второй цикл похож, но я хотел более четко показать, как увеличение значения связано со смещением и как работает арифметика.

Третий и четвертый циклы повторяют процесс, но работают с массивом в стеке, а не на выделенную память в куче.

При печати отдельного изображения char. Это говорит printf выводить все, на что указывает переменная, а не содержимое самой переменной. Это контрастирует с строкой выше, где баланс строки печатается, и перед переменной нет звездочки, потому что printf() просматривает серию местоположений памяти целиком до достижения NULL.

Вот выход на ubuntu 15.10, работающий на i7 (первый и третий выходные петли, начинающиеся со смещения 1, потому что мой выбор цикла for увеличивается в начале цикла, а не do{}while(); для упрощения):

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 
Смежные вопросы