Почему Delphi и Free Pascal обычно предпочитают тип данных с подписью целого типа без знака?
Я не новичок Pascal, но до сих пор я до сих пор не знаю, почему Delphi и Free Pascal обычно объявляет параметры и возвращает значения в виде целых чисел, в то время как я вижу, что они всегда должны быть положительными. Например:
-
Pos()
возвращает тип Integer. Возможно ли быть отрицательным?
-
SetLength()
объявляет параметр NewLength
как тип Integer. Есть ли отрицательная длина для строки?
-
System.THandle
объявлен как Longint. Есть ли отрицательное число для дескрипторов?
Существует множество решений, таких как Delphi и Free Pascal. Какие соображения стояли за этим?
Ответы
Ответ 1
В Pascal целочисленный (подписанный) является базовым типом. Все остальные типы целых чисел являются поддиапазонами целого числа. (это не совсем верно в диалектах Borland, учитывая longint в TP и int64 в Delphi, но достаточно близко).
Важная причина для этого, если промежуточный результат вычислений становится отрицательным, и вы вычисляете с помощью целых чисел без знака, будут проверяться ошибки проверки диапазона, и поскольку большинство старых языков программирования НЕ допускают целые числа с 2-мя дополнениями, результат (с диапазоном выключения) может даже быть поврежденным.
Корпус THandle намного проще. У Delphi не было 32-битного без знака до D4, но только 31-битный кардинал. (поскольку 32-разрядное целое без знака не является поддиапазоном целого числа, более поздние неподписанные int являются подмножеством int64, который перенес проблему на uint64, который был добавлен только в D2010 или около того)
Таким образом, во многих местах в заголовках подписываются типы, в которых winapi использует неподписанные типы, возможно, чтобы избежать 32-го бита, который был поврежден в этих версиях, и пользовательский застрял.
Но случай winapi отличается от общего случая.
Добавлено позже. Некоторые реализации Pascal (и Modula2/3) обходят эту ловушку, устанавливая целое число с размером, большим, чем словосочетание, и требуют, чтобы все числовые типы объявляли правильный поддиапазон, как в ниже программы.
Первое содержит основное предположение о том, что все является подмножеством целого числа, а второе позволяет компилятору масштабировать почти все вниз, чтобы соответствовать регистрам, особенно если у процессора есть некоторые операции для операций с более чем словами. (например, x86, где 32-битный * 32-разрядный mul дает 64-битный результат или может обнаруживать переполнения словаря, используя бит состояния (например, для генерации исключений для добавлений без добавления полного слова 2 слова)
var x : 0..20;
y : -10..10;
begin
// any expression of x and y has a range -10..20
Ответ 2
Ну, для начала THandle
объявляется неправильно. Он неподписан в заголовках Windows и должен быть в Delphi. На самом деле я думаю, что это было исправлено в недавнем выпуске Delphi.
Я бы предположил, что предпочтение подписываться без знака в значительной степени историческое и не особенно важно. Однако я могу привести пример, где это важно. Рассмотрим цикл for:
for i := 0 to Count-1 do
Если i
не указано и Count
равно 0, этот цикл работает от 0 до $FFFFFFFF
, который не является тем, что вы хотите. Использование целочисленной переменной цикла исключает эту проблему.
Паскаль является жертвой своего синтаксиса здесь. Эквивалентный цикл C или С++ не имеет таких проблем
for (unsigned int i=0; i<Count; i++)
из-за синтаксической разницы и использования оператора сравнения в качестве условия остановки.
Это также может быть причиной того, что Length()
в строке или динамическом массиве возвращает значение со знаком. И поэтому для согласованности SetLength()
должен принимать подписанные значения. И учитывая, что для индексирования строк используется возвращаемое значение Pos()
, оно также должно быть подписано.
Вот еще одно обсуждение темы: Должен ли я использовать целые числа без знака для подсчета членов?
Конечно, я размышляю здесь дико. Возможно, не было никакого дизайна, и по привычке был установлен и закреплен прецедент использования подписанных значений.
Ответ 3
- Некоторые функции поиска по строке возвращают -1, когда ничего не найдено.
- Я считаю, что причиной этого является то, что MaxInt составляет 2 ГБ, что является максимальным размером для строк в 32-битном Delphi. Это потому, что один процесс может иметь до 2 ГБ памяти
Ответ 4
Существует множество причин использования знаковых целых чисел, даже некоторых, которые могут применяться, когда вы не собираетесь возвращать отрицательное значение.
Представьте, что я пишу код, который вызывает Pos, и я хочу сделать математику с результатами. Вы предпочли бы иметь отрицательный результат (Pos('x',s)-5)
поднять исключение диапазона проверки, переполнить и стать очень большим числом без знака около 4 миллиардов, или пойти отрицательно, если Pos('x',s)
возвращает 1
? Любой из них является источником проблем для новых пользователей, которые редко думают об этих случаях, но давняя традиция заключается в том, что, используя результаты Integer
, ваша работа должна проверять отрицательные и нулевые результаты и не использовать их в качестве смещений строк. Существует преимущество для начинающих и для продвинутых программистов, при использовании Integer, а не при наличии "отрицательных" значений, перекачиваемых и становящихся большими значениями без знака или исключениями диапазона.
Во-вторых, помните, что в начале программирования обычно вводятся типы Integer
(signed) задолго до того, как вы вводите неподписанные типы типа Cardinal
. Начинающие часто работают с такими функциями, как Pos
, и имеет смысл использовать тип, создающий наименее недружелюбный набор побочных эффектов. Нет никаких отрицательных побочных эффектов, чтобы иметь диапазон больше, чем тот, который вам абсолютно необходим (диапазон, который вам, вероятно, нужен для Pos, равен 1 до максимальной длины строки в delphi). В 32-разрядном Delphi есть нулевое преимущество для использования типа Cardinal
для Pos, и определенно ARE уменьшает его выбор.
Однако, как только вы доберетесь до 64-битного delphi, теоретически у вас могут быть строки LARGER, чем Integer может удерживать, и переход к Cardinal не устранит все ваши потенциальные проблемы. Однако вероятность того, что у кого-то есть строка 2+ GB, вероятно, равна нулю, а 64-разрядный компилятор Delphi не допускает строку >2 GB
, во всяком случае. В моем тестировании я могу добиться почти 1 ГБ строки в 64-битном Delphi. Таким образом, практический предел длины для строки Win64 составляет около миллиарда (1073741814) символов, который использует почти 2 ГБ фактической ОЗУ. В этом лимите я либо получаю EIntOverflow
, либо EAccessViolation
, и кажется, что я поражаю ошибки библиотеки времени выполнения Delphi (RTL), а не правильно определенные лимиты, поэтому ваш пробег может меняться.