Не нарушит ли это язык или существующий код, если мы добавим безопасные сравнения со знаком / без знака в C / C ++? - PullRequest
20 голосов
/ 13 августа 2010

После прочтения этого вопроса о сравнениях со знаком / без знака (они появляются каждые пару дней, я бы сказал):

Я удивлялся, почему у нас нет должных подписанных беззнаковых сравнений, а вместо этого этот ужасный беспорядок?Возьмите вывод из этой маленькой программы:

#include <stdio.h>
#define C(T1,T2)\
 {signed   T1 a=-1;\
 unsigned T2 b=1;\
  printf("(signed %5s)%d < (unsigned %5s)%d = %d\n",#T1,(int)a,#T2,(int)b,(a<b));}\

 #define C1(T) printf("%s:%d\n",#T,(int)sizeof(T)); C(T,char);C(T,short);C(T,int);C(T,long);
int main()
{
 C1(char); C1(short); C1(int); C1(long); 
}

Скомпилированный с моим стандартным компилятором (gcc, 64bit), я получу это:

char:1
(signed  char)-1 < (unsigned  char)1 = 1
(signed  char)-1 < (unsigned short)1 = 1
(signed  char)-1 < (unsigned   int)1 = 0
(signed  char)-1 < (unsigned  long)1 = 0
short:2
(signed short)-1 < (unsigned  char)1 = 1
(signed short)-1 < (unsigned short)1 = 1
(signed short)-1 < (unsigned   int)1 = 0
(signed short)-1 < (unsigned  long)1 = 0
int:4
(signed   int)-1 < (unsigned  char)1 = 1
(signed   int)-1 < (unsigned short)1 = 1
(signed   int)-1 < (unsigned   int)1 = 0
(signed   int)-1 < (unsigned  long)1 = 0
long:8
(signed  long)-1 < (unsigned  char)1 = 1
(signed  long)-1 < (unsigned short)1 = 1
(signed  long)-1 < (unsigned   int)1 = 1
(signed  long)-1 < (unsigned  long)1 = 0

Если я скомпилирую для 32-битного, результатто же самое, за исключением того, что:

long:4
(signed  long)-1 < (unsigned   int)1 = 0

«Как?»все это легко найти: просто перейдите к разделу 6.3 стандарта C99 или главе 4 C ++ и найдите пункты, описывающие, как операнды преобразуются в общий тип, и это может сломаться, если общий тип интерпретирует отрицательные значения.

А как же "Почему?"Как мы видим, символ «<» дает сбой в 50% случаев, также он зависит от конкретных размеров типов, поэтому он зависит от платформы.Вот несколько моментов, которые следует учитывать: </p>

  • Процесс преобразования и сравнения на самом деле не является ярким примером для Правила наименьшего сюрприза

  • НадеюсьНе верьте, что существует код, основанный на предположении, что (short)-1 > (unsigned)1 и не написано террористами.

  • Это все ужасно, когда выВы находитесь в C ++ с шаблоном кода, потому что вам нужно волшебство черты типа, чтобы связать правильный "<". </p>


В конце концов, сравнивая подписанные и беззнаковые значения разных типов is легко реализовать:

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 

Предварительная проверка обходится дешево и также может быть оптимизирована компилятором, если a> = 0 может быть статически доказано.

Итак, вот мой вопрос:

Не нарушит ли язык или существующий код, если мы добавим безопасные сравнения со знаком / без знака в C / C ++?

(«Будет ли это нарушать язык» означает, что нам нужно было бы внести значительные изменения в различные части языка, чтобы учесть это изменение)


ОБНОВЛЕНИЕ: Я выполнилэто на моем старом добром Turbo-C ++ 3.0 и получил такой вывод:

char:1
(signed  char)-1 < (unsigned  char)1 = 0

Почему (signed char)-1 < (unsigned char) == 0 здесь?

Ответы [ 6 ]

11 голосов
/ 13 августа 2010

Мой ответ только для C.

В C нет типа, который бы мог вместить все возможные значения всех возможных целочисленных типов.Ближайший C99 - это intmax_t и uintmax_t, и их пересечение охватывает только половину их соответствующего диапазона.

Следовательно, вы не можете реализовать математическое сравнение значений, например x <= y, сначала преобразовав x и y к общему типу и затем выполняем простую операцию.Это серьезное отклонение от общего принципа работы операторов.Это также нарушает интуицию того, что операторы соответствуют вещам, которые, как правило, представляют собой отдельные инструкции в общем аппаратном обеспечении.

Даже если бы вы добавили эту дополнительную сложность к языку (и дополнительную нагрузку для разработчиков реализации), это не имело быочень хорошие свойстваНапример, x <= y все равно не будет эквивалентно x - y <= 0.Если бы вы хотели получить все эти замечательные свойства, вам нужно было бы сделать целые числа произвольного размера частью языка.

Я уверен, что существует множество старых кодов Unix, возможно, некоторые из них работают на вашем компьютере, чтопредполагает, что (int)-1 > (unsigned)1.(Хорошо, возможно, это было написано борцами за свободу; -)

Если вы хотите lisp / haskell / python / $ favourite_language_with_bignums_built_in, вы знаете, где его найти ...

8 голосов
/ 13 августа 2010

Да, это нарушит язык / существующий код.Язык, как вы заметили, тщательно определяет поведение, когда операнды со знаком и без знака используются вместе.Такое поведение с операторами сравнения является существенным для некоторых важных идиом, таких как:

if (x-'0' < 10U)

Не говоря уже о вещах типа (сравнение равенства):

size_t l = mbrtowc(&wc, s, n, &state);
if (l==-1) ... /* Note that mbrtowc returns (size_t)-1 on failure */

В качестве отступления, определяя "натуральный"Поведение для смешанных сравнений со знаком и без знака также может привести к значительному снижению производительности, даже в программах, которые в настоящее время используют такие сравнения безопасными способами, где они уже имеют свое «естественное» поведение из-за ограничений на ввод, которые компилятору пришлось бы испытывать затруднения.определение (или не может быть в состоянии определить вообще).При написании собственного кода для этих тестов я уверен, что вы уже видели, как будет выглядеть снижение производительности, и это не очень красиво.

7 голосов
/ 13 августа 2010

Я не думаю, что это сломало бы язык, но да, оно могло бы сломать некоторый существующий код (и, вероятно, его было бы трудно обнаружить на уровне компилятора).

Существует гораздо больше кода, написанного на C и C ++, чем вы и я вместе можем представить (некоторые из них могут быть даже написаны террористами).

Полагаясь на «утверждение, что (short)-1 > (unsigned)1» может быть сделано кем-то непреднамеренно. Существует много кода на C, касающегося сложных битовых манипуляций и подобных вещей. Вполне возможно, что некоторые программисты могут использовать текущее поведение сравнения в таком коде. (Другие люди уже предоставили хорошие примеры такого кода, а код еще проще, чем я ожидал).

Текущее решение состоит в том, чтобы вместо этого предупреждать о таких сравнениях и оставлять решение за программистом, который, как мне кажется, в духе того, как работают C и C ++. Кроме того, решение этой проблемы на уровне компилятора повлечет за собой снижение производительности, и к этому программисты на C и C ++ чрезвычайно чувствительны. Два теста вместо одного могут показаться вам незначительной проблемой, но, вероятно, существует много кода на C, где это может быть проблемой. Это может быть решено, например, вынуждая предыдущее поведение с помощью явных приведений к общему типу данных - но это опять-таки потребует внимания программиста, поэтому это не лучше, чем простое предупреждение.

1 голос
/ 13 августа 2010

Я думаю, что C ++ похож на Римскую империю. Он большой и слишком устоявшийся, чтобы исправить вещи, которые его уничтожат.

c ++ 0x - и boost - являются примерами ужасного ужасного синтаксиса - типа ребенка, которого могут любить только его родители - и они далеки от простого элегантного (но строго ограниченного) c ++ 10 лет назад.

Дело в том, что к тому времени, когда кто-то "исправил" что-то столь же ужасно простое, как сравнения целочисленных типов, достаточно устаревшего и существующего кода на языке c ++, чтобы можно было просто назвать его новым языком.

И однажды сломанный, есть еще много всего, что также имеет право на обратную фиксацию.

0 голосов
/ 30 июня 2014

Если бы сравнение между целочисленными типами сравнивало фактические математические значения, я бы хотел, чтобы то же самое происходило для сравнений между целым числом и плавающей точкойИ сравнение точных значений произвольного 64-битного целого числа и произвольного числа с плавающей запятой двойной точности довольно сложно.Но тогда компилятор, вероятно, будет лучше, чем я.

0 голосов
/ 06 сентября 2013

Единственный способ для языка определить правила, которые могут приблизиться к соблюдению принципа наименьшего сюрприза во время выполнения при использовании объединения операндов разных типов языка C, - это запретить неявные преобразования типов впо крайней мере, в некоторых контекстах (смещая «сюрприз» на «почему это не скомпилируется?» и уменьшая вероятность возникновения непредвиденных ошибок в будущем), определяйте несколько типов для каждого формата хранения (например, варианты с переносом и без переносакаждый целочисленный тип), или оба.

Наличие нескольких типов для каждого формата хранения, например, версии с оберткой и без обертки для 16-разрядных целых чисел со знаком и без знака, может позволить компилятору различать «Яиспользуя 16-битное значение здесь, в случае, если это делает вещи более эффективными, но оно никогда не выйдет за пределы диапазона 0-65535 , и мне было бы все равно, что произошло, если бы оно сделало ) "и" Яиспользуя 16-битное значение, которое нужно обернуть в 65535, оно становится отрицательным ".В последнем случае компилятор, который использовал 32-битный регистр для такого значения, должен был бы маскировать его после каждой арифметической операции, но в первом случае компилятор мог бы пропустить это.Что касается вашего конкретного желания, смысл сравнения между длинным без знака-оболочки и длинным знаком без упаковки без знака будет понятен, и компилятору будет целесообразно сгенерировать мульти-инструкциюпоследовательность, необходимая для того, чтобы это произошло (поскольку преобразование отрицательного числа в неупаковывающее unsigned long было бы неопределенным поведением, компилятор, определяющий поведение операторов сравнения для этих типов, не конфликтовал бы ни с чем другим, что можно было бы указать).

К сожалению, помимо того, что компилятор генерирует предупреждения для сравнений со смешанным операндом, я на самом деле не вижу многого, что можно сделать с языком C, поскольку он существует без добавления новых типов к нему, как описано выше;хотя я бы расценил добавление таких новых типов как улучшение, я не задержал бы дыхание.

...