Важность ассемблера
В C++11 была введена очень важная и полезная вещь: строго-типизированный enum. Я не буду перечислять все полезности, в сети полно статей на эту тему. Тем более, на дворе 2018 год, пора бы уже всем этим пользоваться.
Одна из хороших вещей в enum class
ах это возможность выбора типа, используемого для хранения данных (зачастую называется enum base
). Это выглядит следующим образом:
enum class Foo : std::uint32_t {
a = 0,
b,
c,
};
Это важное дополнениие для некоторых компиляторов, которые не совсем следуют стандарту. Я столкнулся с этой проблемой в ARM Compiler 5. У нас есть специализированная навигационная микросхема с двумя ядрами NeuroMatrix (NMC) и ядром ARM1176JZF-S под названием BBP2 или СНП-ВП. NMC это достаточно хороший цифровой сигнальный процессор (DSP), но с особенностью в виде 32-битного байта.
The byte is a unit of digital information that most commonly consists of eight bits. … it is the smallest addressable unit of memory in many computer architectures.Wiki.
Минимально адресуемая единица в NMC — 32 бита (4 байта в ARM-адресации). Это означает, что если мы собирается записать 8 бит во внутреннюю память, микросхема зависнет, что приводит к очень неприятным багам, которые трудно выловить.
Простой пример: нам нужно осуществить межпроцессорное взаимодействие через разделяемую память. Одним из лучших способов является размещения объекта структуры в некотором месте с фиксированным адресом, чтобы оба процессора имели к ней доступ. Например, рассмотрим функцию, которая будет менять состояние энумератора:
enum class Foo {
a = 0,
b,
c,
};
struct Bar {
Foo foo = Foo::a;
} bar;
void Meow() {
bar.foo = Foo::b;
}
Почему-то это не работает. Причину можно понять после беглого взгляда на сгенерированный ассемблер:
_Z4Meowv PROC
MOV r0,#1
LDR r1,|L0.52|
STRB r0,[r1,#0] ; bar // <-------
BX lr
ENDP
Упс, инструкция STRB
. Если вы не знакомы с ассемблером ARM, STRB
означает запись байта (doc). Как я и сказал раньше, программа из-за этого упадёт.
Давайте поменяем тип энумератора, в соответствии с кодом в самом начале статьи:
_Z4Meowv PROC
MOV r0,#1
LDR r1,|L0.52|
STR r0,[r1,#0] ; bar // <-------
BX lr
ENDP
Ура, больше не падает! Однако, что будет, если мы (по ряду причин) застряли на pre-C++11 компиляторе с простыми энумераторами? Насколько я могу судить, есть только один выход: ввести заглушку со значением, которое расширит диапазон значений до 32-битного целого числа:
enum OldFoo {
a = 0,
b,
c,
dummy = INT_MAX
};
Кстати, это может быть просто баг в ARM Compiler 5, поскольку стандарт довольно чётко говорит по этому поводу:
For a scoped enumeration type, the underlying type is int if it is not explicit ly specified (ISO/ IEC 14882 :2017(E), 10.2.5)
Также следует отметить, что другие копиляторы не страдают от этой проблемы (число 123 используется для большей наглядности):
x86-64 GCC trunk:
_Z4Meowv:
mov DWORD PTR bar[rip], 123
nop
ret
x86-64 clang trunk:
Meow(): # @Meow()
push rbp
mov rbp, rsp
mov dword ptr [bar], 123
pop rbp
ret
x86-64 icc 18.0.0:
Meow():
push rbp #13.13
mov rbp, rsp #13.13
mov DWORD PTR bar[rip], 123 #14.2
leave #15.1
ret #15.1
Ссылка на compiler explorer тут.
Я надеюсь, что мне удалось убедить вас, что есть ряд багов, которые можно с лёгкостью заметить, прочитав сгенерированный ассемблер.