Спасибо всем за ответы, они даже, наверно, правильные, но окончательно всё прояснилось, когда мне ответили на Хабре. Привожу сокращённый ответ, вдруг кому понадобится.
Да, действительно, процессор обрабатывает не байты, слова и прочее, а биты и их совокупности разных размеров. Что из себя представляют эти биты - решают разработчики. Чтобы поместить в 8 бит число со знаком, и чтобы можно было с этим числом проводить арифметические операции, разработчики договорились о нескольких форматах хранения. Сначала придумали прямой код, потом обратный, а потом дополнительный. Об этом можно почитать на Википедии.
Так вот, в C\C++ числа со знаком хранятся в дополнительном коде. То есть переменная типа intptr_t будет именно так храниться. Свойства дополнительного кода позволяют нам гарантированно получить разницу между [адресом 2147483640] и [адресом 2147483640+100 байт], даже если [адрес 2147483640+100 байт] превышает максимально допустимый размер в 2147483646 для intptr_t. Это возможно за счёт того, что пространство чисел в дополнительном коде специальным образом закольцовано.
А числа без знака хранятся как uintptr_t в 8 битах в прямом коде, т.е. от 0 до 4294967295. С ними всё понятно и так.
Теперь ответ на вопрос "зачем нужен intptr_t если есть uintptr_t?". uintptr_t нужен, чтобы СРАВНИВАТЬ два указателя. Т.к. числа в нём представлены линейно растущей последовательностью прямого кода, это становится очевидным. При этом сравнивать значения типа intptr_t НЕЛЬЗЯ, т.к. из-за особенностей представления (дополнительный код) указатель на больший адрес может быть представлен битовой последовательностью, которая МЕНЬШЕ, чем указатель на меньший адрес.