C Language
Montaż w linii
Szukaj…
Uwagi
Asemblowanie to praktyka polegająca na dodawaniu instrukcji asemblacji w środku kodu źródłowego C. Żadna norma ISO C nie wymaga obsługi wbudowanego zestawu. Ponieważ nie jest to wymagane, składnia zestawu wbudowanego różni się w zależności od kompilatora. Mimo że jest zwykle obsługiwany, istnieje bardzo niewiele powodów, aby używać złożenia wbudowanego i wiele powodów, aby tego nie robić.
Plusy
- Wydajność Pisząc szczegółowe instrukcje montażu dla operacji, można osiągnąć lepszą wydajność niż kod zestawu wygenerowany przez kompilator. Należy pamiętać, że te wzrosty wydajności są rzadkie. W większości przypadków można osiągnąć lepszy wzrost wydajności po prostu przez przestawienie kodu C, aby optymalizator mógł wykonać swoje zadanie.
- Interfejs sprzętowy Sterownik urządzenia lub kod startowy procesora może potrzebować trochę kodu asemblera, aby uzyskać dostęp do prawidłowych rejestrów i zagwarantować, że pewne operacje będą wykonywane w określonej kolejności z określonym opóźnieniem między operacjami.
Cons
- Przenośność kompilatora Nie można zagwarantować, że składnia dla zestawu wbudowanego jest taka sama w różnych kompilatorach. Jeśli piszesz kod z wbudowanym zestawem, który powinien być obsługiwany przez różne kompilatory, użyj makr preprocesora (
#ifdef
), aby sprawdzić, który kompilator jest używany. Następnie napisz osobną sekcję montażu wbudowanego dla każdego obsługiwanego kompilatora. - Przenośność procesora Nie można pisać wbudowanego zestawu dla procesora x86 i oczekiwać, że będzie on działał na procesorze ARM. Zestaw wbudowany jest przeznaczony do pisania dla określonego procesora lub rodziny procesorów. Jeśli masz wbudowany zestaw, który ma być obsługiwany na różnych procesorach, użyj makr preprocesora, aby sprawdzić, dla którego procesora jest kompilowany kod i wybrać odpowiednią sekcję kodu zestawu.
- Przyszłe zmiany wydajności Zespół wbudowany może być pisany z oczekiwaniem opóźnień na podstawie określonej częstotliwości taktowania procesora. Jeśli program jest skompilowany dla procesora z szybszym zegarem, kod zestawu może nie działać zgodnie z oczekiwaniami.
gcc Podstawowa obsługa asm
Podstawowa obsługa zestawu z gcc ma następującą składnię:
asm [ volatile ] ( AssemblerInstructions )
gdzie AssemblerInstructions
to kod bezpośredniego zestawu dla danego procesora. Zmienne słowo kluczowe jest opcjonalne i nie działa, ponieważ gcc nie optymalizuje kodu w ramach podstawowej instrukcji asm. Instrukcje AssemblerInstructions
mogą zawierać wiele instrukcji montażu. Podstawowa instrukcja asm jest używana, jeśli masz procedurę asm, która musi istnieć poza funkcją C. Poniższy przykład pochodzi z podręcznika GCC:
/* Note that this code will not compile with -masm=intel */
#define DebugBreak() asm("int $3")
W tym przykładzie możesz użyć DebugBreak()
w innych miejscach kodu i DebugBreak()
instrukcję asemblera int $3
. Zauważ, że chociaż gcc nie zmodyfikuje żadnego kodu w podstawowej instrukcji asm, optymalizator może nadal przenosić kolejne instrukcje asm. Jeśli masz wiele instrukcji asemblera, które muszą wystąpić w określonej kolejności, dołącz je do jednej instrukcji asm.
gcc Rozszerzona obsługa asm
Rozszerzona obsługa asm w gcc ma następującą składnię:
asm [volatile] ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
asm [volatile] goto ( AssemblerTemplate
:
: InputOperands
: Clobbers
: GotoLabels)
gdzie AssemblerTemplate
jest szablonem instrukcji asemblera, OutputOperands
to dowolne zmienne C, które mogą być modyfikowane przez kod zestawu, InputOperands
to dowolne zmienne C używane jako parametry wejściowe, Clobbers
to lista lub rejestry, które są modyfikowane przez kod asemblera, a GotoLabels
to wszelkie etykiety instrukcji goto, które mogą być użyte w kodzie asemblera.
Rozszerzony format jest używany w funkcjach C i jest bardziej typowym zastosowaniem złożenia wbudowanego. Poniżej znajduje się przykład z jądra Linux do zamiany bajtów 16-bitowych i 32-bitowych liczb dla procesora ARM:
/* From arch/arm/include/asm/swab.h in Linux kernel version 4.6.4 */
#if __LINUX_ARM_ARCH__ >= 6
static inline __attribute_const__ __u32 __arch_swahb32(__u32 x)
{
__asm__ ("rev16 %0, %1" : "=r" (x) : "r" (x));
return x;
}
#define __arch_swahb32 __arch_swahb32
#define __arch_swab16(x) ((__u16)__arch_swahb32(x))
static inline __attribute_const__ __u32 __arch_swab32(__u32 x)
{
__asm__ ("rev %0, %1" : "=r" (x) : "r" (x));
return x;
}
#define __arch_swab32 __arch_swab32
#endif
Każda sekcja asm używa zmiennej x
jako parametru wejściowego i wyjściowego. Funkcja C zwraca następnie zmanipulowany wynik.
Dzięki rozszerzonemu formatowi asm gcc może optymalizować instrukcje asemblera w bloku asm zgodnie z tymi samymi regułami, których używa do optymalizacji kodu C. Jeśli chcesz, aby sekcja asm pozostała nietknięta, użyj volatile
słowa kluczowego dla sekcji asm.
gcc Zespół wbudowany w makrach
Możemy umieścić instrukcje montażu wewnątrz makra i używać makra tak, jakbyś wywołał funkcję.
#define mov(x,y) \
{ \
__asm__ ("l.cmov %0,%1,%2" : "=r" (x) : "r" (y), "r" (0x0000000F)); \
}
/// some definition and assignment
unsigned char sbox[size][size];
unsigned char sbox[size][size];
///Using
mov(state[0][1], sbox[si][sj]);
Korzystanie z wbudowanych instrukcji montażu osadzonych w kodzie C może poprawić czas działania programu. Jest to bardzo pomocne w sytuacjach krytycznych czasowo, takich jak algorytmy kryptograficzne, takie jak AES. Na przykład dla prostej operacji zmiany biegów, która jest wymagana w algorytmie AES, możemy zastąpić bezpośrednią instrukcję montażu Rotate Right
operatorem C >>
.
W implementacji „AES256” w funkcji „AddRoundKey ()” mamy kilka takich instrukcji:
unsigned int w; // 32-bit
unsigned char subkey[4]; // 8-bit, 4*8 = 32
subkey[0] = w >> 24; // hold 8 bit, MSB, leftmost group of 8-bits
subkey[1] = w >> 16; // hold 8 bit, second group of 8-bit from left
subkey[2] = w >> 8; // hold 8 bit, second group of 8-bit from right
subkey[3] = w; // hold 8 bit, LSB, rightmost group of 8-bits
/// subkey <- w
Po prostu przypisują wartość bitu w
do tablicy subkey
.
Możemy zmienić trzy shift + przypisać i jedno przypisać wyrażenie C za pomocą tylko jednego zespołu Rotate Right
.
__asm__ ("l.ror %0,%1,%2" : "=r" (* (unsigned int *) subkey) : "r" (w), "r" (0x10));
Ostateczny wynik jest dokładnie taki sam.