Сравнение указателей в C. Они подписаны или не подписаны?
Привет, я уверен, что это обычный вопрос, но я не могу найти ответ, когда ищу его. Мой вопрос в основном касается двух указателей. Я хочу сравнить их адреса и определить, один из них больше другого. Я ожидаю, что все адреса не будут подписаны во время сравнения. Правда ли это, и варьируется ли она между C89, C99 и C++? Когда я компилирую с gcc, сравнение не подписано.
Если у меня есть два указателя, которые я сравниваю так:
char *a = (char *) 0x80000000; //-2147483648 or 2147483648 ?
char *b = (char *) 0x1;
затем a
лучше. Это гарантировано стандартом?
Изменить, чтобы обновить то, что я пытаюсь сделать. У меня есть ситуация, когда я хотел бы определить, что, если есть арифметическая ошибка, это не заставит указатель выйти за пределы. Прямо сейчас у меня есть начальный адрес массива и конечный адрес. И если есть ошибка, и вычисление указателя неверно, и за пределами допустимых адресов памяти для массива, я хотел бы убедиться, что нарушения доступа не происходит. Я считаю, что могу предотвратить это, сравнивая подозрительный указатель, который был возвращен другой функцией, и определяя, находится ли он в допустимом диапазоне массива. Вопрос об отрицательных и положительных адресах связан с тем, могу ли я проводить сравнения, как обсуждалось выше в моем первоначальном вопросе.
Я ценю ответы до сих пор. Исходя из моих правок, вы сказали бы, что я делаю неопределенное поведение в 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);
Спасибо всем!
4 ответа
Сравнение указателей не может быть подписано или не подписано. Указатели не являются целыми числами.
Язык C (как и C++) определяет сравнения указателей только для указателей, которые указывают на один и тот же агрегат (структура или массив). Порядок естественный: указатель, который указывает на элемент с меньшим индексом в массиве, меньше. Указатель, который указывает на член структуры, объявленный ранее, меньше. Вот и все.
Вы не можете юридически сравнивать произвольные указатели в C/C++. Результат такого сравнения не определен. Если вы заинтересованы в сравнении числовых значений адресов, хранящихся в указателях, вы обязаны сначала вручную преобразовать указатели в целочисленные значения. В этом случае вам придется решить, использовать ли целочисленный тип со знаком или без знака (intptr_t
или же uintptr_t
). В зависимости от того, какой тип вы выберете, сравнение будет "подписанным" или "без знака".
Целочисленное преобразование в указатель полностью определяется реализацией, поэтому оно зависит от используемой вами реализации.
Тем не менее, вам разрешено только относительное сравнение указателей, которые указывают на части одного и того же объекта (в основном, на подобъекты одной и той же структуры или элементы одного и того же массива). Вам не разрешено сравнивать два указателя с произвольными, совершенно не связанными объектами.
Из проекта стандарта C++ 5.9:
Если два указателя
p
а такжеq
одного и того же типа указывают на разные объекты, которые не являются членами одного и того же объекта или элементов одного и того же массива или на разные функции, или, если только один из них равен нулю, результатыp<q
,p>q
,p<=q
, а такжеp>=q
не определены.
Итак, если вы приведете числа к указателям и сравните их, C++ даст вам неопределенные результаты. Если вы берете адрес элементов, которые вы можете сравнивать корректно, результаты операций сравнения указываются независимо от подписи типов указателей.
Замечание unspecified не является неопределенным: вполне возможно сравнивать указатели на разные объекты одного и того же типа, которые не находятся в той же структуре или массиве, и вы можете ожидать некоторого самосогласованного результата (в противном случае было бы невозможно использовать такие указатели в качестве ключей в деревьях, или для сортировки vector
таких указателей, бинарный поиск вектора и т. д., где последовательный интуитивно понятный в целом <
заказ необходим).
Обратите внимание, что в очень старых стандартах C++ поведение было неопределенным - как в черновой версии WG14/N1124 2005 года, на которую ссылаются Андрюдски под ответом Джеймса Макнеллиса -
Чтобы дополнить другие ответы, сравнение между указателями, которые указывают на разные объекты, зависит от стандарта.
В C99 (ISO / IEC 9899: 1999 (E)), §6.5.8:
5 [...] Во всех остальных случаях поведение не определено.
В C++ 03 (ISO / IEC 14882: 2003 (E)), §5.9:
-Другие сравнения указателей не определены.
Я знаю, что некоторые ответы здесь говорят, что вы не можете сравнивать указатели, если они не указывают на одну и ту же структуру, но это красная сельдь, и я попытаюсь объяснить, почему. Один из ваших указателей указывает на начало вашего массива, другой на конец, поэтому они указывают на одну и ту же структуру. Специалист по языку может сказать, что если ваш третий указатель указывает вне объекта, сравнение не определено, поэтому x >= array.start
возможно true
для всех x
, Но это не проблема, поскольку в момент сравнения C++ не может знать, не встроен ли массив в еще большую структуру. Кроме того, если ваше адресное пространство является линейным, как это должно быть в наши дни, сравнение указателей будет реализовано как (не) целочисленное сравнение со знаком, поскольку любая другая реализация будет медленнее. Даже во времена сегментов и смещений (дальнее) сравнение указателей осуществлялось путем сначала нормализации указателя, а затем сравнения их как целых чисел.
Все это сводится к тому, что если ваш компилятор в порядке, сравнение указателей, не беспокоясь о знаках, должно работать, если все, что вас волнует, это то, что указатель указывает в массиве, так как компилятор должен сделать указатели подписанными или без знака в зависимости от того, какая из двух границ может находиться между C++ объектом.
Различные платформы ведут себя по-разному в этом вопросе, поэтому C++ должен оставить это на усмотрение платформы. Есть даже платформы, в которых оба адреса около 0 и 80..00h не сопоставимы или уже заняты при запуске процесса. В этом случае это не имеет значения, если вы последовательны в этом.
Иногда это может вызвать проблемы совместимости. Как пример, в Win32 указатели не подписаны. Раньше было так, что из 4 ГБ адресного пространства только нижняя половина (точнее 10000h ... 7FFFFFFFh, из-за раздела назначения NULL-указателя) была доступна для приложений; высокие адреса были доступны только ядру. Это привело к тому, что некоторые люди поместили адреса в подписанные переменные, и их программы продолжали работать, так как старший бит всегда был 0. Но потом пришел /3GB
переключатель, который сделал почти 3 ГБ доступными для приложений (точнее 10000h ... BFFFFFFFh), и приложение будет аварийно завершать работу или работать неправильно.
Вы явно заявляете, что ваша программа будет только для Windows, которая использует беззнаковые указатели. Тем не менее, может быть, вы измените свое мнение в будущем, и с помощью intptr_t
или же uintptr_t
это плохо для мобильности. Мне также интересно, стоит ли вам вообще это делать... если вы индексируете в массив, может быть безопаснее сравнивать индексы. Предположим, например, что у вас есть массив 1 ГБ с 1500000h ... 41500000h, состоящий из 16,384 элементов по 64 КБ каждый. Предположим, вы случайно посмотрели индекс 80000 - явно вне диапазона. Вычисление указателя даст 39D00000h, так что проверка вашего указателя позволит, даже если это не должно.