Странная сборка из массива 0-инициализация - PullRequest
28 голосов
/ 10 февраля 2009

Вдохновленный вопросом Разница в инициализации и обнулении массива в c / c ++? , я решил на самом деле изучить сборку, в моем случае, оптимизированной сборки выпуска для Windows Mobile Professional (процессор ARM от оптимизирующего компилятора Microsoft). То, что я нашел, было несколько удивительным, и мне интересно, сможет ли кто-нибудь пролить свет на мои вопросы по этому поводу.

Эти два примера рассматриваются:

byte a[10] = { 0 };

byte b[10];
memset(b, 0, sizeof(b));

Они используются в одной и той же функции, поэтому стек выглядит так:

[ ] // padding byte to reach DWORD boundary
[ ] // padding byte to reach DWORD boundary
[ ] // b[9] (last element of b)
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ] // b[0] = sp + 12 (stack pointer + 12 bytes)
[ ] // padding byte to reach DWORD boundary
[ ] // padding byte to reach DWORD boundary
[ ] // a[9] (last element of a)
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ] // a[0] = sp (stack pointer, at bottom)

Сгенерированная сборка с моими комментариями:

; byte a[10] = { 0 };

01: mov   r3, #0        // r3 = 0
02: mov   r2, #9        // 3rd arg to memset: 9 bytes, note that sizeof(a) = 10
03: mov   r1, #0        // 2nd arg to memset: 0-initializer
04: add   r0, sp, #1    // 1st arg to memset: &a[1] = a + 1, since only 9 bytes will be set
05: strb  r3, [sp]      // a[0] = r3 = 0, sets the first element of a
06: bl    memset        // continue in memset

; byte b[10];
; memset(b, 0, sizeof(b));

07: mov   r2, #0xA      // 3rd arg to memset: 10 bytes, sizeof(b)
08: mov   r1, #0        // 2nd arg to memset: 0-initializer
09: add   r0, sp, #0xC  // 1st arg to memset: sp + 12 bytes (the 10 elements
                        // of a + 2 padding bytes for alignment) = &b[0]
10: bl    memset        // continue in memset

Теперь меня смущают две вещи:

  1. Какой смысл в строках 02 и 05? Почему бы просто не дать & a [0] и 10 байтов в memset?
  2. Почему байты заполнения не инициализированы 0? Это только для заполнения в структурах?

Редактировать: мне было слишком любопытно, чтобы не проверять случай структуры:

struct Padded
{
    DWORD x;
    byte y;
};

Ассемблер для его инициализации 0:

; Padded p1 = { 0 };

01: mov   r3, #0
02: str   r3, [sp]
03: mov   r3, #0
04: str   r3, [sp, #4]

; Padded p2;
; memset(&p2, 0, sizeof(p2));

05: mov   r3, #0
06: str   r3, [sp]
07: andcs r4, r0, #0xFF
08: str   r3, [sp, #4]

Здесь мы видим в строке 04, что заполнение действительно происходит, поскольку используется str (в отличие от strb). Верно?

Ответы [ 3 ]

14 голосов
/ 10 февраля 2009

Причина для строк 2 и 5 в том, что вы указали 0 в инициализаторе массива. Компилятор инициализирует все константы, а затем дополняет остальные, используя memset. Если бы вы поместили два нуля в ваш инициализатор, вы бы увидели, что он strw (слово вместо байта), а memset 8 байт.

Что касается заполнения, оно используется только для выравнивания доступа к памяти - данные не должны использоваться в нормальных условиях, поэтому memsetting это расточительно.

Редактировать: Кстати, я могу ошибаться в предположении о strw выше. 99% моего опыта ARM - это изменение кода, сгенерированного GCC / LLVM на iPhone, поэтому моё предположение может не перейти на MSVC.

11 голосов
/ 10 февраля 2009

Оба бита кода не содержат ошибок. Две упомянутые строки не являются умными, но вы просто доказываете, что этот компилятор генерирует неоптимальный код.

Байты заполнения обычно инициализируются, только если это упрощает сборку или ускоряет код. Например, если у вас есть заполнение между двумя заполненными нулями элементами, часто также легче заполнить нулями заполнение. Кроме того, если у вас есть заполнение в конце и ваш memset () оптимизирован для многобайтовой записи, возможно, будет быстрее перезаписать это заполнение.

8 голосов
/ 11 февраля 2009

Некоторое быстрое тестирование показывает, что компилятор Microsoft x86 генерирует другую сборку, если список инициализаторов пуст, по сравнению с тем, когда он содержит ноль. Может быть, их ARM компилятор тоже. Что произойдет, если вы сделаете это?

byte a[10] = { };

Вот список сборок, который я получил (с параметрами /EHsc /FAs /O2 в Visual Studio 2008). Обратите внимание, что включение нуля в список инициализаторов заставляет компилятор использовать невыровненные обращения к памяти для инициализации массива, в то время как в пустой версии списка инициализаторов и версии memset() используются выравниваемые обращения к памяти:

; unsigned char a[10] = { };

xor eax, eax
mov DWORD PTR _a$[esp+40], eax
mov DWORD PTR _a$[esp+44], eax
mov WORD PTR _a$[esp+48], ax

; unsigned char b[10] = { 0 };

mov BYTE PTR _b$[esp+40], al
mov DWORD PTR _b$[esp+41], eax
mov DWORD PTR _b$[esp+45], eax
mov BYTE PTR _b$[esp+49], al

; unsigned char c[10];
; memset(c, 0, sizeof(c));

mov DWORD PTR _c$[esp+40], eax
mov DWORD PTR _c$[esp+44], eax
mov WORD PTR _c$[esp+48], ax
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...