Технический термин для обозначения блока в покрытии кода — базовый блок. Чтобы скопировать данные непосредственно из статьи Википедии:
Код в базовом блоке имеет одну точку входа, что означает, что ни один код в нем не является местом назначения инструкции перехода в любом месте программы, и имеет одну точку выхода, что означает, что только последняя инструкция может заставить программу начать выполнение кода в другой базовый блок. В этих условиях всякий раз, когда выполняется первая инструкция в базовом блоке, остальные инструкции обязательно выполняются ровно один раз по порядку.
Базовый блок важен для покрытия кода, потому что мы можем вставить проверку в начале базового блока. При попадании в этот зонд мы знаем, что будут выполнены все следующие инструкции в этом базовом блоке (благодаря свойствам базового блока).
К сожалению, с компиляторами (и особенно с оптимизациями) не всегда очевидно, как исходный код сопоставляется с базовыми блоками. Самый простой способ узнать это — посмотреть на сгенерированную сборку. Например, давайте посмотрим на ваши исходные main
и testfunction
:
Для main
я вижу сборку ниже (перемежается с первоисточником). Аналогично тому, что Питер делает здесь, я отметил, где начинаются основные блоки.
int main()
{
013B2D20 push ebp <--- Block 0 (initial)
013B2D21 mov ebp,esp
013B2D23 sub esp,40h
013B2D26 push ebx
013B2D27 push esi
013B2D28 push edi
testfunction(-1);
013B2D29 push 0FFFFFFFFh
013B2D2B call testfunction (013B10CDh)
013B2D30 add esp,4 <--- Block 1 (due to call)
testfunction(1);
013B2D33 push 1
013B2D35 call testfunction (013B10CDh)
013B2D3A add esp,4 <--- Block 2 (due to call)
}
013B2D3D xor eax,eax
013B2D3F pop edi
013B2D40 pop esi
013B2D41 pop ebx
013B2D42 mov esp,ebp
013B2D44 pop ebp
013B2D45 ret
Мы видим, что main
имеет три основных блока: один начальный блок, а два других из-за вызовов функций. Глядя на код, это кажется разумным. testfunction
немного жестче. Просто глядя на источник, кажется, что есть три блока:
- Вход в функциональный и логический тест (
input > 0
)
- Условие истинной ветви (
return 1
)
- Условие ложной ветви (
return 0
)
Однако из-за фактической сгенерированной сборки имеется четыре блока. Я предполагаю, что вы создали свой код с отключенными оптимизациями. Когда я строю с VS2010 в конфигурации Debug (оптимизации отключены), я вижу следующую дизассемблирование для testfunction
:
int testfunction(int input)
{
013B2CF0 push ebp <--- Block 0 (initial)
013B2CF1 mov ebp,esp
013B2CF3 sub esp,40h
013B2CF6 push ebx
013B2CF7 push esi
013B2CF8 push edi
if (input > 0) {
013B2CF9 cmp dword ptr [input],0
013B2CFD jle testfunction+18h (013B2D08h)
return 1;
013B2CFF mov eax,1 <--- Block 1 (due to jle branch)
013B2D04 jmp testfunction+1Ah (013B2D0Ah)
}
else {
013B2D06 jmp testfunction+1Ah (013B2D0Ah) <--- Not a block (unreachable code)
return 0;
013B2D08 xor eax,eax <--- Block 2 (due to jmp branch @ 013B2D04)
}
}
013B2D0A pop edi <--- Block 3 (due to being jump target from 013B2D04)
013B2D0B pop esi
013B2D0C pop ebx
013B2D0D mov esp,ebp
013B2D0F pop ebp
013B2D10 ret
Здесь у нас есть четыре блока:
- Вход в функцию
- Условие истинной ветви
- Условие ложной ветви
- Общий эпилог функции (очистка стека и возврат)
Если бы компилятор продублировал эпилог функции как в ветвях условия true, так и в ветвях условия false, вы бы увидели только три блока. Также, что интересно, компилятор вставил ложную инструкцию jmp
в 013B2D06
. Поскольку это недостижимый код, он не рассматривается как базовый блок.
В общем, весь этот анализ является излишним, поскольку общая метрика покрытия кода скажет вам то, что вам нужно знать. Этот ответ был просто для того, чтобы подчеркнуть, почему количество блоков не всегда очевидно или что ожидается.
person
Chris Schmich
schedule
11.02.2011