Сравнение указателей в C. Являются ли они подписанными или неподписанными?
Привет. Я уверен, что это должен быть общий вопрос, но я не могу найти ответ, когда я его ищу. Мой вопрос в основном касается двух указателей. Я хочу сравнить их адреса и определить, больше ли один из них. Я бы ожидал, что во время сравнения все адреса будут неподписанными. Это правда, и отличается ли это от C89, C99 и С++? Когда я компилирую с gcc, сравнение не имеет знака.
Если у меня есть два указателя, которые я сравниваю следующим образом:
char *a = (char *) 0x80000000; //-2147483648 or 2147483648 ?
char *b = (char *) 0x1;
Тогда a
больше. Гарантируется ли это стандартом?
Изменить, чтобы обновить то, что я пытаюсь сделать. У меня есть ситуация, когда я хотел бы определить, что если есть арифметическая ошибка, она не заставит указателя выйти за пределы. Прямо сейчас у меня есть начальный адрес массива и конечный адрес. И если есть ошибка, и вычисление указателя неверно, и за пределами допустимых адресов памяти для массива, я хотел бы убедиться, что нарушение доступа не происходит. Я считаю, что могу предотвратить это, сравнивая подозрительный указатель, который был возвращен другой функцией, и определяя, находится ли он в допустимом диапазоне массива. Вопрос об отрицательных и позитивных адресах связан с тем, могу ли я проводить сравнения, как обсуждалось выше в моем первоначальном вопросе.
Я ценю ответы до сих пор. Основываясь на моем редактировании, вы скажете, что то, что я делаю, - это поведение undefined в gcc и msvc? Это программа, которая будет работать только в Microsoft Windows.
Здесь приведен более упрощенный пример:
char letters[26];
char *do_not_read = &letters[26];
char *suspect = somefunction_i_dont_control(letters,26);
if( (suspect >= letters) && (suspect < do_not_read) )
printf("%c", suspect);
Другое редактирование, после прочтения ответа AndreyT, кажется правильным. Поэтому я сделаю что-то вроде этого:
char letters[26];
uintptr_t begin = letters;
uintptr_t toofar = begin + sizeof(letters);
char *suspect = somefunction_i_dont_control(letters,26);
if( ((uintptr_t)suspect >= begin) && ((uintptr_t)suspect < toofar ) )
printf("%c", suspect);
Спасибо всем!
Ответы
Ответ 1
Сравнение указателей не может быть подписано или не подписано. Указатели не являются целыми числами.
Язык C (как и C++) определяет относительные сравнения указателей только для указателей, которые указывают на один и тот же агрегат (структура или массив). Порядок естественен: указатель, который указывает на элемент с меньшим индексом в массиве, меньше. Указатель, который указывает на ранее объявленный член структуры, меньше. Это.
Вы не можете юридически сравнивать произвольные указатели в C/C++. Результат такого сравнения не определен. Если вы заинтересованы в сравнении числовых значений адресов, хранящихся в указателях, вы обязаны сначала вручную преобразовать указатели в целочисленные значения. В этом случае вам придется решить, использовать ли целочисленный тип со intptr_t
или без знака (intptr_t
или uintptr_t
). В зависимости от того, какой тип вы выберете, сравнение будет "подписанным" или "без знака".
Ответ 2
Преобразование целых чисел в указатель целиком полностью определено, поэтому оно зависит от используемой реализации.
Тем не менее, вам разрешено реляционно сравнивать указатели, указывающие на части одного и того же объекта (в основном, на подобъекты одной и той же структуры или элементы того же массива). Вам не разрешено сравнивать два указателя на произвольные, полностью несвязанные объекты.
Ответ 3
Из проекта С++ Standard 5.9:
Если два указателя p
и q
того же типа указывают на разные объекты которые не являются членами одного и того же объекта или элементов одного и того же массива или к различным функциям, или если только один из них является нулевым, результаты из p<q
, p>q
, p<=q
и p>=q
не определены.
Итак, если вы набрасываете числа на указатели и сравниваете их, С++ дает неуказанные результаты. Если вы берете адрес элементов, которые вы можете сравнить, результаты операций сравнения указываются независимо от подписанности типов указателей.
Примечание unspecified is not undefined: вполне возможно сравнить указатели на разные объекты того же типа, которые не находятся в одной структуре или массиве, и вы можете ожидать некоторого самосогласованного результата (иначе это было бы невозможно использовать такие указатели, как ключи в деревьях, или сортировать vector
таких указателей, двоичный поиск вектора и т.д., где требуется последовательный интуитивный общий порядок <
).
Обратите внимание, что в очень старых С++-стандартах поведение было undefined - как 2005 проект WG14/N1124 и ссылки edrewdski под Джеймсом Макнеллисом answer -
Ответ 4
Я знаю, что несколько ответов здесь говорят, что вы не можете сравнивать указатели, если они не указывают на одну и ту же структуру, но на то, что красная селедка, и я попытаюсь объяснить, почему. Один из ваших указателей указывает на начало вашего массива, а другой - на конец, поэтому они указывают на одну и ту же структуру. Адвокат языка может сказать, что если ваш третий указатель указывает вне объекта, сравнение undefined, поэтому x >= array.start
может быть true
для всех x
. Но это не проблема, так как в момент сравнения С++ не может знать, не массив ли встроен в еще большую структуру. Кроме того, если ваше адресное пространство является линейным, как и в наши дни, сравнение с указателем будет реализовано как (un) подписанное целочисленное сравнение, так как любая другая реализация будет медленнее. Даже в периоды сегментов и смещений сравнение (дальний) указатель было реализовано путем первой нормализации указателя, а затем сравнения их как целых чисел.
Что все это сводится к тому, что, если ваш компилятор в порядке, сравнение указателей, не беспокоясь о знаках, должно работать, если все, о чем вы заботитесь, это то, что указатель указывает внутри массива, поскольку компилятор должен сделать указатели, подписанные или неподписанные, в зависимости от того, какая из двух границ может иметь объект С++.
Различные платформы ведут себя по-другому в этом вопросе, поэтому С++ должен оставить его на платформе. Существуют даже платформы, в которых оба адреса около 0 и 80..00h не могут быть отображены или уже приняты при запуске процесса. В этом случае это не имеет значения, если вы согласны в этом.
Иногда это может вызвать проблемы совместимости. Например, в указателях Win32 нет знака. Теперь это было так, что для адресного пространства 4 ГБ для приложений было доступно только нижняя половина (точнее 10000h... 7FFFFFFFh из-за раздела назначения NULL-указателя); высокие адреса были доступны только ядру. Это заставило некоторых людей поместить адреса в подписанные переменные, и их программы продолжали работать, так как высокий бит всегда был 0. Но затем появился коммутатор /3GB
, который сделал доступным для приложений почти 3 ГБ (точнее 10000h... BFFFFFFFh) и приложение будет рушиться или вести себя беспорядочно.
Вы явно заявляете, что ваша программа будет только для Windows, которая использует неподписанные указатели. Однако, возможно, вы передумаете в будущем, и использование intptr_t
или uintptr_t
плохо для переносимости. Я также задаюсь вопросом, нужно ли вообще делать это... если вы индексируете в массив, возможно, более безопасно сравнивать индексы. Предположим, например, что у вас есть массив 1 ГБ при 1500000h... 41500000h, состоящий из 16 384 элементов по 64 kB каждый. Предположим, вы случайно просмотрели индекс 80 000 – явно вне пределов досягаемости. Вычисление указателя даст 39D00000h, поэтому ваша проверка указателя позволит это, хотя это не должно быть.
Ответ 5
Чтобы дополнить другие ответы, сравнение между указателями, которые указывают на разные объекты, зависит от стандарта.
В C99 (ISO/IEC 9899: 1999 (E)), §6.5.8:
5 [...] Во всех остальных случаях поведение не определено.
В С++ 03 (ISO/IEC 14882: 2003 (E)), §5.9:
-Other сравнения указателей не определены.