Сегодня товарищ redp озадачил меня интересным вопросом. Дескать, если современные компиляторы такие умные, то почему GCC не в состоянии преобразовать даже элементарный макрос инверсии байт двойного слова в ассемблерную инструкцию bswap?
Речь идет о коде вроде этого:
#include <time.h>
typedef unsigned int u32 ;
#define U8TO32_BE(p)
(((u32)((p)[0]) << 24) |
((u32)((p)[1]) << 16) |
((u32)((p)[2]) << 8) |
((u32)((p)[3]) ))
int main ( ) {
u32 x = ( u32 ) time ( 0 ) ;
printf ( «U8TO32_BE(%08x) = %08x n » , x ,
U8TO32_BE ( ( unsigned char * ) & x ) ) ;
return 0 ;
}
Действительно, как Visual Studio 2008, так и GCC 4.6 не в состоянии распознать в макросе U8TO32_BE простую команду bswap. Конечно, можно воспользоваться ассемблерными вставками или нестандартными расширениями языка типа _byteswap_ulong (не знаю, так ли оно называется в GCC), но эти методы плохи тем, что делают код зависимым от конкретного компилятора или архитектуры процессора.
Я переписал программу следующим образом:
#include <time.h>
typedef unsigned int u32 ;
#define BSWAP32(x) (
(((x) & 0xFF) << 24) |
(((x) & 0xFF00) << 8) |
(((x) & 0xFF0000) >> 8) |
(((x) & 0xFF000000) >> 24))
int main ( ) {
u32 x = ( u32 ) time ( 0 ) ;
printf ( «BSWAP32(%08x) = %08x n » , x , BSWAP32 ( x ) ) ;
return 0 ;
}
И посмотрел ассемблерный код, генерируемый GCC:
Необходимо указать тип процессора, потому что в i386 команды bswap не было. По умолчанию GCC ничего и никак не оптимизирует, потому флаг оптимизации также необходим. В результате получаем файл bswap.s следующего содержания:
. section . rodata . str1 . 1 , «aMS» , @progbits , 1
. LC0 :
. string «BSWAP32(%08x) = %08xn»
. section . text . startup , «ax» , @progbits
. p2align 4 ,, 15
. globl main
. type main , @function
main :
. LFB1 :
. cfi_startproc
pushl % ebp
. cfi_def_cfa_offset 8
. cfi_offset 5 , — 8
movl % esp , % ebp
. cfi_def_cfa_register 5
andl $ — 16 , % esp
subl $ 16 , % esp
movl $ 0 , ( % esp )
call time
movl $ . LC0 , ( % esp )
movl % eax , % edx
bswap % edx
movl % eax , 4 ( % esp )
movl % edx , 8 ( % esp )
call printf
xorl % eax , % eax
leave
. cfi_restore 5
. cfi_def_cfa 4 , 4
ret
. cfi_endproc
. LFE1 :
. size main , .- main
. ident «GCC: 4.6.2 20110729 (prerelease)»
Как видите, bswap появился. Что интересно, GCC 4.2 (который вышел в 2008-м году) так не умеет.
Мораль в том, что современные компиляторы хоть и умны, но не настолько, чтобы распознать в серии получения указателей на переменные и обращения к элементам массива простую перестановку байт (еще раз смотрим, что и как делает U8TO32_BE). Чем проще код вы пишите, тем легче компилятору будет его оптимизировать. Например, когда вы пишете цикл, обходящий массив, не нужно извращаться с указателями. Используйте обычные индексы.