A switch conditional statement makes it possible to select one of the branches of execution, depending on the value of the control expression. The value of the control expression is compared to an integer or character constant in a list. If a coincidence is detected , the statements associated with the constant are executed.
The form of this statement can be represented as follows :
switch (expression) { case constant 1: <statements> break; case constant 2: <statements> break; ... default: }
To implement a switch statement in the assembler, you can use a comparison command for each case and a jump to an appropriate label in the program (Listing 4.11).
... mov EAX, DWORD PTR N cmp EAX, VALUE_1 je BRANCH_1 cmp EAX, VALUE_2 je BRANCH_2 cmp EAX, VALUE_3 je BRANCH_3 ... cmp EAX, VALUE_N je BRANCH_N ... BRANCH_1: <statements> BRANCH_2: <statements> BRANCH_N: <statements> ...
It is often convenient to call subroutines processing the condition rather than make conditional jumps (Listing 4.12).
... mov EAX, DWORD PTR N cmp EAX, VALUE_1 jne BRANCH_1 call PROC_1 jmp EXIT BRANCH_1: cmp EAX, VALUE_2 jne BRANCH_2 call PROC_2 jmp EXIT BRANCH_2: cmp EAX, VALUE_3 jne BRANCH_3 call PROC_3 jmp EXIT BRANCH_3: cmp EAX, VALUE_4 jne EXIT call PROC_4 ... EXIT: cmp EAX, VALUE_2 je BRANCH_2 cmp EAX, VALUE_3 je BRANCH_3 ... cmp EAX, VALUE_N je BRANCH_N ...
Now, we will consider a C++ program that adds, subtracts, or multiplies two integers depending on the selection of one of three cases. The source code of the console application is shown in Listing 4.13.
// SWITCH_EXM.cpp : Defines the entry point for the console application #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int i1, i2, isw, ires; while (true) { printf("\nEnter first number (i1): "); scanf("%d", &i1); printf("Enter second number (i2): "); scanf("%d", &i2); printf ("Choice: 1 i1+i2, 2 i1 i2, 3 i1*i2\n"); scanf("%d", &isw); switch (isw) { case 1: ires = i1+i2; break; case 2: ires = i1 i2; break; case 3: ires = i1*i2; break; default: break; } printf("Result=%d\n", ires); } return 0; }
It would be useful to write some effective assembly code for this task. If you got rid of the switch case branching, the program performance would increase significantly. As noted earlier, the command set of Pentium II and higher includes the cmov and fcmov commands that make it possible to implement the switch case algorithm. Modify the previous listing so that the cmov command can be used. Use the C++ .NET inline assembler for this purpose. The source code of the program is shown in Listing 4.14.
// SWITCH_EXM.cpp : Defines the entry point for the console application #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int i1, i2, isw, ires; int iadd, isub, im; while (true) { printf("\n"); printf("Enter first number (i1): "); scanf("%d", &i1); printf("Enter second number (i2): "); scanf("%d", &i2); printf ("Choice: 1 i1+i2, 2 i1 i2, 3 il*i2\n"); scanf("%d", &isw); iadd = i1+i2; isub = i1 i2; im = i1*i2; _asm { xor EDX, EDX mov EAX, isw cmp EAX, 1 cmove EDX, iadd cmp EAX, 2 cmove EDX, isub cmp EAX, 3 cmove EDX, im mov ires, EDX } printf("Result=%d\n", ires); } return 0; }
As you can see from Listing 4.14, the switch statement is replaced with its assembly analog, a group of commands in the _asm { } block. To implement an assembly version, you should declare a few auxiliary variables in the source code:
int iadd, isub, im;
Write the following statements just before the assembly block:
iadd = i1+i2; isub = i1 i2; im = il*i2;
These statements are necessary for the cmove command. Depending on flags set with the previous command, this command moves the contents of a register or a memory cell to another register. Before you use the cmov command, make sure it is supported by the processor. This is done with the cpuid command.
The window of the application is shown in Fig. 4.2.
This completes the discussion of C++ .NET logical structures and their optimization with the assembler. You can easily modify any example from this chapter and use it in your own applications.