значение против типа: код для определения, если переменная подписана или нет - PullRequest
2 голосов
/ 19 сентября 2011

Я наткнулся на этот вопрос на форуме.Ответ примерно такой:

#define ISUNSIGNED(a) (a >= 0 && ~a >= 0) 

//Alternatively, assuming the argument is to be a type, one answer would use type casts: 

#define ISUNSIGNED(type) ((type)0 - 1 > 0)

У меня есть несколько вопросов по этому поводу. Почему нам нужно проверить ~a >= 0?О чем вообще второе решение?Я не поняла утверждение: «аргумент должен быть типом».Что еще более важно автор заявляет, что сначала #define не будет работать в ANSI C (но будет работать в K & R C).Почему нет?

Ответы [ 6 ]

8 голосов
/ 19 сентября 2011
#define ISUNSIGNED(a) (a >= 0 && ~a >= 0) 

Для положительного значения со знаком, a >= 0 будет истинным (очевидно), а ~a >= 0 будет ложным, поскольку мы перевернули биты, так что теперь бит знака установлен, что приводит к отрицательному значению. Таким образом, все выражение ложно.

Для отрицательного значения со знаком, a >= 0 будет ложным (очевидно), а остальная часть выражения не будет оцениваться; общий результат для выражения ложен.

Для значения без знака a >= 0 будет всегда истинным (очевидно, поскольку значения без знака не могут быть отрицательными). Если мы перевернем биты, то ~a >= 0 также будет истинным, поскольку даже если самый старший бит (знаковый бит) установлен в 1, он все равно будет рассматриваться как положительное значение.

Таким образом, выражение возвращает true, если исходное значение и его побитовая инверсия оба положительны, то есть это значение без знака.

#define ISUNSIGNED(type) ((type)0 - 1 > 0)

Это следует вызывать с типом, а не со значением: ISUNSIGNED(int) или ISUNSIGNED(unsigned int), например.

Для int код расширяется до

((int)0 - 1 > 0)  

, что неверно, поскольку -1 не больше 0.

Для unsigned int код расширяется до

((unsigned int)0 - 1 > 0) 

Литералы со знаком 1 и 0 в выражении переводятся в unsigned, чтобы соответствовать первому 0, поэтому все выражение оценивается как сравнение без знака. 0 - 1 в арифметике без знака будет оборачиваться, что приведет к наибольшему возможному значению без знака (все биты установлены в 1), которое больше 0, поэтому результат равен true.

Относительно того, почему он будет работать с K & R C, но не с ANSI C, может быть, эта статья может пролить некоторый свет:

Когда неподписанный символ или неподписанный шорт расширены, тип результата int, если int достаточно велико, чтобы представлять все значения меньший тип. В противном случае тип результата - беззнаковое целое. Значение правило сохранения дает наименее неожиданный арифметический результат для большинства выражения.

Полагаю, это означает, что при сравнении, например, unsigned short с 0 значение без знака преобразуется в signed int, что нарушает поведение макроса.

Вероятно, вы можете обойти это, имея (a-a), который оценивает либо ноль со знаком, либо без знака, в зависимости от ситуации, вместо литерала 0, который всегда подписан.

3 голосов
/ 27 октября 2015

Трюки с переворотом милы, но если вы хотите узнать подпись чего-либо, есть более простые способы сделать это.Подпись - это свойство типа, а не значение.Чтобы определить, является ли переменная подписанной, вы можете спросить своего компилятора, если -1, приведенный к этому типу, меньше 0, например, так:

#define issigned(t) (((t)(-1)) < 0)

Если вы хотите знать это дляконкретная переменная , вы можете спросить у компилятора тип этой переменной:

issigned(typeof(v))
3 голосов
/ 19 сентября 2011

Проверка для ~a >= 0 основана на переключении подписанного бита.Логика такова:

  • , если число отрицательное, оно не может быть без знака, поэтому a >= 0 возвращает false.
  • , если оно положительное, оно либо со знаком, либо без знака.
  • если это без знака, применение ~ a приведет к максимальному значению без знака - a, все еще положительное число без знака.
  • , если оно подписано, бит знака будет отрицаться, приводя котрицательное значение.

На самом деле, я думаю, что это может быть правдой, что a >= 0 && ~a >= 0 и все же число будет подписано.Это потому, что может быть отрицательный ноль.В частности, для одного дополнения значение всех нулей представляет 0, в то время как представление с отрицанием (все 1) будет отрицательным 0 (или представление ловушки).

Существует также проблема целочисленных повышений:

Если int может представлять все значения исходного типа, значение преобразуется в int;в противном случае он конвертируется в беззнаковое целое.Они называются целочисленными акциями.Все остальные типы не изменяются при целочисленных акциях.

Целочисленные продвижения применяются к «объекту или выражению с целочисленным типом, чей целочисленный коэффициент преобразования меньше или равен рангу int и unsigned int» иmsgstr "[a] битовое поле типа _Bool, int, sign int или unsigned int."Они используются как с унарным оператором ~, так и в «обычных арифметических преобразованиях», которые охватывают первое сравнение, поэтому они применяются здесь.

Так что unsigned char будет повышен до int, поэтому становится подписанным, в то время как он был первоначально неподписан.

Например, это даст неправильный результат:

#include<stdio.h>
#define ISUNSIGNED(a) (a >= 0 && ~a >= 0)
void main(void) {
    unsigned char uc = 8;
    printf("%d", ISUNSIGNED(uc));
}
3 голосов
/ 19 сентября 2011

Для первого макроса: если значение положительное (>= 0), его побитовое отрицание должно быть отрицательным в 2-дополнении, если оно опущено. Значение без знака останется положительным:

~64 == -65 (signed)
~64 == 191 (unsigneD)

Для второго: если вы передаете тип марко, он проверяет, может ли этот тип содержать одиночные значения. Для закаленных типов 0-1 == -1, для без знака это положительное значение:

(char) 0-1 == -1
(unsigned char) 0-1 == 255
1 голос
/ 13 апреля 2018

Ответ (a >= 0 && ~a >= 0), предложенный в вопросе, является плохим, поскольку он зависит от целочисленного представления, и, в частности, он не работает в качестве дополнения, если a равен 0 типа со знаком.Вот правильный, переносимый способ выполнения действий.

Во-первых, если T является целочисленным типом, можно использовать следующий макрос:

#define ISUNSIGNED(T) ((T) -1 > 0)

Следовательно, для версии сзначение в качестве аргумента, если тип значения a не менее int (чтобы избежать целочисленных повышений, которые могут изменить подпись типа), можно использовать следующий макрос:

#define ISUNSIGNED(a) (0*(a) - 1 > 0)

Он не зависит от представления целых чисел, поэтому он совместим со всеми представлениями целых чисел, разрешенными стандартом ISO C (два дополнения, одно дополнение и величина знака).

0 голосов
/ 19 сентября 2011

Первый #define работает с именем переменной.Второй #define работает с именем типа.(Очевидно, что если вы намеревались использовать обе программы в одной и той же программе, вам нужно было бы дать им разные имена.)

Я не знаю, почему либо не будет работать с ANSI C.

...