Подтвердить что ты не робот

Добавление указателя против вычитания

$5.7 -

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

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

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

        p1 + p2;       // Error

        p1 - p2;       // OK
}

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

По той же причине в большинстве библиотек есть отдельные объекты TimeSpan и DateTime.

Ответ 3

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

Ответ 4

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

Ответ 5

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

У меня есть два int в памяти в 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 */

Ответ 6

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

В качестве быстрого добавления к ответу @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

Ответ 7

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

Ответ 8

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