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
Примечание: добавление указателя поддерживается в том смысле, что 'T * p =/** /; p = p + 1' (и указывает еще один элемент).Это немного отличается от вашего вопроса, хотя :) –
Многие ответы указывают на то, что разница будет означать количество объектов типа, которые могут быть сохранены между двумя указателями p1 и p2. Что, если указатель имеет тип «void *»? – Chubsdad
@chubsdad: тогда вы не можете их вычесть. – Potatoswatter