Страницы

Поиск по вопросам

среда, 3 октября 2018 г.

Как в C/C++ узнать, является ли тип знаковым или беззнаковым?

Сталкиваясь с типами данных, подобными time_t, size_t и другими, очевидно "численными" (считая, что указатель это тоже число) типами, иногда становится просто интересно, а это signed или unsigned тип?
Как, не роясь во всех include-файлах, где находятся их определения, часто спрятанные в дебрях условных макроподстановок, выяснить это?
По крайней мере для x86_64 GNU/Linux и компилятора gcc/g++ можно использовать простой прием, заключающийся в вычитании единицы из нуля и проверки, меньше ли нуля результат вычитания. Если меньше, то мы имеем signed тип, а иначе он unsigned
Такой прием можно оформить парой макросов:
#define SIGNED_TYPE(typename) ({volatile typename v = 0; volatile typename v1 = v - 1; v > v1;})
определяет знаковость типа по имени типа, а макрос
#define SIGNED_VAR(var) ({volatile typeof(var) v = 0, v1 = v - 1; v > v1;})
по переменной данного типа.
И если с первым макро все хорошо, то второй работает только при компиляции с флагом -std=... , определяющим GNU расширения C/C++ (в т.ч. без флага -std=..., т.е. "по умолчанию" для gcc/g++), а вот для -std=c89, -std=c11 и т.д. не компилируется, поскольку typeof() это GNU расширение.
Такая модификация макроса для определения "знаковости" переменной
#ifndef __STRICT_ANSI__ // GNU extensions #define SIGNED_VAR(var) ({volatile typeof(var) v = 0, v1 = v - 1; v > v1;}) #else #define SIGNED_VAR(var) ({var = 0; var > var - 1;}) #endif
конечно ужасна, во-первых, макрос меняет значение тестируемой переменной, а во-вторых (и это главное), неправильно определяет "знаковость" указателя (правильно -- unsigned) при компиляции в __STRICT_ANSI__ с оптимизацией выше -O1
Маленькая программа для иллюстрации.
// compile gcc/g++ #include #include #include
int main (int ac, char *av[]) { time_t t = 0, t1 = t - 1; size_t s = 0, s1 = s - 1; char *p = 0, *p1 = p - 1; int i = 0, i1 = i - 1, *pp = 0, *pp1 = pp - 1; double d = 0, d1 = d - 1;
printf("time_t: %ssigned
", t1 < t ? "" : "un"); printf("size_t: %ssigned
", s1 < s ? "" : "un"); printf("int: %ssigned
", i1 < i ? "" : "un"); printf("double: %ssigned
", d1 < d ? "" : "un"); printf("char *: %ssigned
", p1 < p ? "" : "un"); printf(" demo for pointers
%p > %p %s
", pp, pp1, pp > pp1 ? "true" : "false"); puts("
check macro");
#define SIGNED_TYPE(typename) ({volatile typename v = 0; volatile typename v1 = v - 1; v > v1;})
printf("SIGNED_TYPE(time_t): %ssigned
", SIGNED_TYPE(time_t) ? "" : "un"); printf("SIGNED_TYPE(size_t): %ssigned
", SIGNED_TYPE(size_t) ? "" : "un"); printf("SIGNED_TYPE(char *): %ssigned
", SIGNED_TYPE(char *) ? "" : "un"); printf("SIGNED_TYPE(int): %ssigned
", SIGNED_TYPE(int) ? "" : "un"); printf("SIGNED_TYPE(double): %ssigned
", SIGNED_TYPE(double) ? "" : "un"); printf("SIGNED_TYPE(long long): %ssigned
", SIGNED_TYPE(long long) ? "" : "un"); printf("SIGNED_TYPE(unsigned char): %ssigned
", SIGNED_TYPE(unsigned char) ? "" : "un");
#ifndef __STRICT_ANSI__ // GNU extensions #define SIGNED_VAR(var) ({volatile typeof(var) v = 0, v1 = v - 1; v > v1;}) #else // UGLY, because change var, WRONG for -std=c++... -std=c... with -Ox #define SIGNED_VAR(var) ({var = 0; var > var - 1;}) #endif
printf("SIGNED_VAR(time_t t): %ssigned
", SIGNED_VAR(t) ? "" : "un"); printf("SIGNED_VAR(size_t s): %ssigned
", SIGNED_VAR(s) ? "" : "un"); printf("SIGNED_VAR(int i): %ssigned
", SIGNED_VAR(i) ? "" : "un"); printf("SIGNED_VAR(double d): %ssigned
", SIGNED_VAR(d) ? "" : "un"); printf("SIGNED_VAR(char *p): %ssigned
", SIGNED_VAR(p) ? "" : "un");
return puts("End") == EOF; }
Какими еще способами можно ответить на данный вопрос и работает ли описанный метод в системах, отличных от x86_64 GNU/Linux gcc/g++ ?
P.S. не стоит рассматривать эту задачку, как к имеющую смысл при практическом программировании, важную для использования API с такими типами, нет, к ней можно относиться только с т.з. удовлетворения естественного любопытства.


Ответ

В С++ для этого есть std::is_signed, std::is_unsigned
В Си можно написать макрос, который будет преобразовывать литерал 0 и выражение -1 к нужному типу:
#define IS_SIGNED_TYPE(type) ((type)0 > (type)-1)
Для переменных можно было бы использовать var^var чтобы получить 0 нужного типа. Но это работает только для целых чисел. Так же нам надо получить (type)-1 что невозможно если использовать только арифметические операции, например тип var^var - 1 - это int По этому надо использовать такое расширение компилятора, например __typeof__
#define IS_SIGNED_VAR(var) (IS_SIGNED_TYPE(__typeof__(var)))

Комментариев нет:

Отправить комментарий