(2 - 4 = -1), когда значение int присваивается указателю в C?

Я не могу понять, почему в этой программе 2 - 4 дает -1, она присваивает значения int указателям, а не адресам, я знаю, но пока я ее компилировал, компилятор выдал несколько предупреждений, но скомпилировал программу, и она выполнилась, но...

Программа

#include<stdio.h>

int main(void) {

    int *p, *q;

    int arr[] = {1,2,3,4};

    // I know p and q are pointers and address should be assigned to them
    // but look at output, why it evaluates (p-q) to -1 while p as 2 and q as 4

    p = arr[1];
    q = arr[3];

    printf("P-Q: %d, P: %d, Q: %d", (p - q), p, q);

    return 0;
}

Это дает

P-Q: -1, P: 2, Q: 4

person anonymous    schedule 27.06.2018    source источник
comment
Какой результат вы ожидали?   -  person Jabberwocky    schedule 27.06.2018
comment
p = arr[1]; и так поведение, определяемое реализацией.   -  person Sourav Ghosh    schedule 27.06.2018
comment
Вы намеренно определяете int *p, *q; вместо int p, q;? Тогда вам следует подробнее объяснить свои мысли.   -  person Yunnosch    schedule 27.06.2018
comment
@Jabberwocky Я ожидаю -2, может быть, я ошибаюсь, поэтому я и спрашиваю, что на самом деле происходит под капотом   -  person anonymous    schedule 27.06.2018
comment
@Yunnosch да намеренно, просто развлекаюсь с C   -  person anonymous    schedule 27.06.2018
comment
Дополнение к дубликату: в вашем случае q содержит 2, а p содержит 4. Как указано в дубликате: вычитание указателя дает количество элементов массива между двумя указателями одного типа. Предполагая, что sizeof int равно 4 на вашей платформе, разница между указателем 4 и 2 меньше, чем sizeof int, поэтому вычитание на самом деле не имеет смысла.   -  person Jabberwocky    schedule 27.06.2018
comment
p и q - два разных указателя, поэтому любые операции между ними не могут быть четко определены.   -  person Prajval M    schedule 27.06.2018
comment
@phuclv, почему %td для разницы указателей? Я думал, что %ld будет достаточно, поскольку предупреждение предполагает это (проверьте мой ответ и давайте улучшим его, если это необходимо).   -  person gsamaras    schedule 27.06.2018
comment
@gsamaras технически использует неправильный спецификатор формата UB. В этом случае, если указатель имеет длину 64 бита, то их печать с помощью %d недопустима. То же самое с их разницей, которая также является 64-битным типом (ptrdiff_t)   -  person phuclv    schedule 27.06.2018
comment
Я согласен, что %d неправильно. Но в качестве исправления я предложил %ld, вместо long int. Интересно, ваше исправление лучше моего @phuclv.   -  person gsamaras    schedule 27.06.2018
comment
@gsamaras нет, использование неправильного спецификатора формата всегда означает UB. Длинный int может иметь тот же размер, что и int, как в 64-битной Windows. Или вам нужно будет использовать long int перед использованием %ld. ptrdiff_t не обязательно long int   -  person phuclv    schedule 27.06.2018
comment
@phuclv вы правы, большое спасибо! Я улучшил свой ответ и задал новый связанный вопрос, проверьте его вон, если хочешь.   -  person gsamaras    schedule 27.06.2018
comment
C11 J.2: Неопределенное поведение [if] Указатели, которые не указывают на один и тот же объект массива или выходят за него, вычитаются   -  person alk    schedule 01.07.2018


Ответы (2)


Строго говоря, то, что происходит, полностью зависит от вашего компилятора и платформы... но давайте предположим, что мы используем типичный компилятор и игнорируем предупреждения.

Давайте еще больше упростим ваш вопрос:

p = 2;
q = 4;

printf("P-Q: %d, P: %d, Q: %d", (p - q), p, q);

который дает тот же дурацкий результат:

P-Q: -1, P: 2, Q: 4

Как указал @gsamaras, мы пытаемся вычесть два указателя. Давайте попробуем и посмотрим, как это может привести к -1:

p - q = (2 - 4) / sizeof(int)
      = (-2)    / 4
      = -1

Я предлагаю попробовать пару ваших собственных значений p и q, чтобы посмотреть, что произойдет.


Примеры с разными p и q:

p - q = ??
==========
0 - 0 =  0
0 - 1 = -1
0 - 2 = -1
0 - 3 = -1
0 - 4 = -1
1 - 0 =  0
1 - 1 =  0
1 - 2 = -1
1 - 3 = -1
1 - 4 = -1
2 - 0 =  0
2 - 1 =  0
2 - 2 =  0
2 - 3 = -1
2 - 4 = -1
3 - 0 =  0
3 - 1 =  0
3 - 2 =  0
3 - 3 =  0
3 - 4 = -1
4 - 0 =  1
4 - 1 =  0
4 - 2 =  0
4 - 3 =  0
4 - 4 =  0

Создано с использованием gcc -fpermissive на:

#include <stdio.h>

int main() {
    printf("p - q = ??\n");
    printf("==========\n");

    for (int i = 0; i < 5; ++i) {
        for (int j = 0; j < 5; ++j) {
            int* p = i;
            int* q = j;

            printf("%d - %d = %2d\n", p, q, (p - q));
        }
    }

    return 0;
}
person Mateen Ulhaq    schedule 27.06.2018
comment
Хороший ответ, надеюсь, Бармаглот не возражает против удаления дубликата, поскольку наши ответы действительно сошлись. - person gsamaras; 27.06.2018
comment
@mateen-ulhaq этот ответ действительно прояснил концепцию, никто из экспертов/профессионалов на самом деле не пытался понять, что то, что я хочу обсудить здесь, вместо того, чтобы находить недостатки в коде, я знал, что этот код плохо отформатирован, и это было намеренно очистить основные понятия о том, как все работает за кулисами - person anonymous; 27.06.2018
comment
Мы все пытаемся помочь @Mr.Anonymous. Я рад, что Матин предоставил то, что вы искали. Однако вы могли бы прокомментировать мой ответ, попросив конкретных указаний (совет на будущее =)). Тем не менее, мне все еще интересно, как вы смогли скомпилировать и запустить опубликованный вами код. - person gsamaras; 27.06.2018
comment
@gsamaras Я знаю, что здесь все пытаются помочь, я просто хотел оценить ответ, который помог мне, и никогда не хотел обесценивать чужой вклад. Если вы обиделись, извините. это не было моим намерением. - person anonymous; 27.06.2018
comment
Конечно, я не обижаюсь на @Mr.Anonymous. Я просто хочу посоветовать, ура! - person gsamaras; 27.06.2018
comment
@gsamaras да, он успешно скомпилирован с предупреждениями i.gyazo.com/a1bb8344aa18db1f77db3749b457773b.png - person anonymous; 27.06.2018
comment
Действительно ли применяется округление перед вычитанием адреса? Зачем компилятору это делать? Ограничено ли это архитектурами, в которых доступ к случайным невыровненным адресам запрещен? - person Gerhardh; 27.06.2018
comment
@Gerhardh Я думал об архитектуре, ограниченной 32-битным выровненным доступом ... Однако я попробовал 4 - 2 в своей системе, что дало 0. Это говорит о том, что 2 не округляется до 0. Я отредактирую свой ответ. - person Mateen Ulhaq; 27.06.2018
comment
Даже для архитектуры с ограниченным доступом недопустимый адрес вызовет UB, и зачем компилятору добавлять дополнительный код для UB? Я бы предположил, что округление является частью целочисленного деления. Но тогда он должен просто отсечь дробную часть. - person Gerhardh; 27.06.2018
comment
Вывод @MateenUlhaq снова вызывает путаницу, можете ли вы объяснить, когда он округляется в меньшую и в большую сторону? - person anonymous; 27.06.2018
comment
С gcc целочисленное деление всегда округляется в меньшую сторону: -1 / 4 = -1. То же самое с -2, -3, -4. - person Mateen Ulhaq; 27.06.2018
comment
@MateenUlhaq это не деление, а сдвиг вправо на 2, который всегда округляется в меньшую сторону -1 >> 2 = -1 никто не использует div для деления на степень числа 2. И вы все равно получите UB с этими %d - person phuclv; 28.06.2018

В повторяющемся вопросе упоминается, что:

вычитание указателя дает количество элементов массива между двумя указателями одного типа

Подробнее об этом читайте в путанице с вычитанием указателя.

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

main.c: In function ‘main’:
main.c:12:7: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
     p = arr[1];
       ^
main.c:13:7: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
     q = arr[3];
       ^
main.c:15:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ [-Wformat=]
     printf("P-Q: %d, P: %d, Q: %d", (p - q), p, q);
            ^
main.c:15:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘int *’ [-Wformat=]
main.c:15:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 4 has type ‘int *’ [-Wformat=]

Ошибки все равно будут. Для предупреждений я просто использовал флаг -Wall.


Чтобы ваш код имел смысл, вы можете просто объявить p и q как простые int, а не как указатели.

Или вы можете сделать это:

p = &arr[1];
q = &arr[3];

printf("P-Q: %td, P: %p, Q: %p", (p - q), (void *)p, (void *)q);

и получить что-то вроде этого:

P-Q: -2, P: 0x7ffdd37594d4, Q: 0x7ffdd37594dc

Обратите внимание, что я использовал %td для печати результата вычитания указателей.

person gsamaras    schedule 27.06.2018