Вычитание NULL-указателя из обычного указателя генерирует арифметическое смещение вправо - PullRequest
6 голосов
/ 09 апреля 2019

Вот код C.

int main() {
  int i = 10, *p = &i;
  printf("%ld", p - (int *) NULL);
}

Для арифметической части указателя, и 'gcc', и 'clang' генерируют инструкцию 'sar rax, 2' в своих выходных данных сборки.Может ли кто-нибудь объяснить, как арифметика указателя в этом случае связана с арифметическим сдвигом вправо.

1 Ответ

6 голосов
/ 09 апреля 2019

Сдвиг вправо на 2 - это быстрый способ деления на 4. 4 - это ваш int размер.

Расстояние двух указателей до int - это расстояние двух указателей char, соответствующих указателям int, деленным на размер int (помните, что при добавлении целого числа к указателю целое число масштабируется на размер указателя-цели, поэтому при выполнении различий необходимо отменить это масштабирование).

Технически, вы не должны вычитать два не связанных между собой указателя (или печатать разницу с "%ld" вместо правильного "%zd"), так как это неопределенное поведение - стандарт позволяет вам различать только указатели, указывающие на один и тот же объект или только что прошел Тем не менее, общая int* -дифференцирующая функция, которая сама по себе не имеет такого неопределенного поведения:

#include <stddef.h>

ptrdiff_t diff_int_ptr(int *A, int *B)
{
    return A-B;
}

все равно будет переводиться в эквивалент

ptrdiff_t diff_int_ptr(int *A, int *B)
{
    return ((char*)A - (char*)B) >> lg2(sizeof(int));
    //strength reduced from: return ((char*)A - (char*)B) / sizeof(int)
    //which is possible iff sizeof(int) is a power of 2
}

на оптимизирующем компиляторе ( годовая ссылка для x86-64 gcc и clang ), поскольку сдвиг обычно в 10 раз быстрее, чем деление на современной машине.

...