Сложение указателя против вычитания - PullRequest
17 голосов
/ 30 августа 2010

5,7 $ -

"[..] Кроме того, либо оба операнда должны иметь арифметический тип или тип перечисления, либо один операнд должен быть указателем на полностью определенный тип объекта, а другой должен иметь целочисленный тип или тип перечисления.

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

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

        p1 + p2;       // Error

        p1 - p2;       // OK
}

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

Ответы [ 8 ]

29 голосов
/ 30 августа 2010

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

18 голосов
/ 30 августа 2010

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

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

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

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

4 голосов
/ 30 августа 2010

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

3 голосов
/ 30 августа 2010

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

2 голосов
/ 30 августа 2010

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

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

Вы можете , однако добавьте указатель к целому числу, чтобы получить другой указатель.Это то, что вы делаете, когда индексируете в массив: p [4] означает * (p + 4), что означает «вещь, хранящаяся на адресе 4 единицы после этого адреса».определить "степень" арифметической операции, присваивая каждому указателю значение 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 */
1 голос
/ 05 мая 2016

N.B. Здесь нет претензий к стандартам С.

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

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

Так что, если у вас есть указатель на последовательность последовательных областей памяти в куче или массив областей памяти в стеке (имя переменной которого уменьшается до указателя), эти указатели (действительный указатель и тот, который распадается на указатель) будет указывать на первый вопрос о расположении памяти (т. е. элемент [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
0 голосов
/ 18 августа 2015

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

0 голосов
/ 30 августа 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...