Существует самый эффективный и портативный способ, но он не получает никаких наград за красоту.
Можно предположить, что MSB целого числа со знаком всегда устанавливается, если оно отрицательное. Это 100% переносимое предположение, даже если принять во внимание экзотические форматы подписи (дополнение, величина знака). Поэтому самый быстрый способ - просто замаскировать MSB из целого числа.
MSB любого целого числа находится в местоположении CHAR_BIT * sizeof(n) - 1;
. В типичной 32-битной основной системе это будет, например, 8 * 4 - 1
= 31.
Итак, мы можем написать такую функцию:
_Bool is_signed (int n)
{
const unsigned int sign_bit_n = CHAR_BIT * sizeof(n) - 1;
return (_Bool) ((unsigned int)n >> sign_bit_n);
}
На x86-64 gcc 9.1 (-O3) это приводит к очень эффективному коду:
is_signed:
mov eax, edi
shr eax, 31
ret
Преимущество этого метода также состоит в том, что, в отличие от кода, такого как x < 0
, он не рискует быть переведенным в "ветвящиеся, если отрицательные" инструкции при переносе.
Полный пример:
#include <limits.h>
#include <stdio.h>
_Bool is_signed (int n)
{
const unsigned int sign_bit_n = CHAR_BIT * sizeof(n) - 1;
return (_Bool) ((unsigned int)n >> sign_bit_n);
}
int main (void)
{
int n = -1;
const char SIGNS[] = {' ', '-'};
char sign = SIGNS[is_signed(n)];
putchar(sign);
}
Разборка (x86-64 gcc 9.1 (-O3)):
is_signed:
mov eax, edi
shr eax, 31
ret
main:
sub rsp, 8
mov rsi, QWORD PTR stdout[rip]
mov edi, 45
call _IO_putc
xor eax, eax
add rsp, 8
ret