From: http://blog.csdn.net/orbit/article/details/2120086
I don't know when the switch-case statement is synonymous with the bad taste of the Code. When I write the code, I carefully avoid it and frown when I see the switch-case in other people's code, think about it. The switch-case statement is not the root of the bad code taste. The bad taste comes from bad code (structure) design, such as too many switch-case branches, or multiple switch-case nesting, etc. These will lead to reduced code readability. If the code style is poor and the code is not aligned, the bad taste will be quite big.
The short switch-case should be used again, but it is best to solve the long switch-case with too many branches. So what is long and short? I don't know. I just want to judge whether the entire switch-case block can be fully displayed in a window on the display with the lowest resolution. There are many ways to resolve long switch-case. Using function encapsulation or macro to replace the case block is a temporary solution. Using table-driven is usually an effective way to treat this stubborn disease, this article describes how to use the table-driven method to resolve long switch-case.
Let's use an example to illustrate the problem. If we want to write a driver for a system, the system has defined the multiplexing interface (MUX) as follows ):
Status driveriocontrol (uint function_no, pvoid para_in, pvoid para_out)
The user-layer program calls the driver by reusing interfaces. The function number is function_no. The driver is responsible for implementing the specific driveriocontrol () function to complete the corresponding functions. This is a typical scenario where switch-case is used. First, let's look at a solution that uses switch-case:
Status driveriocontrol (uint function_no, pvoid para_in, pvoid para_out) {status RC; Switch (function_no) {Case processa: rc = processa (para_in, para_out); break; Case processb: rc = processb (para_in, para_out); break; Case processc: rc = processc (para_in, para_out); break ;.......... default: rc = un_support; break} return RC;} status processa (pvoid para_in, pvoid para_out) {// some code ....} status processb (pvoid para_in, pvoid para_out) {// some code ....} status processc (pvoid para_in, pvoid para_out) {// some code ....}
................
This scheme is quite satisfactory. However, if the driver is complicated and has many functions, the length of the driveriocontrol function code is quite impressive, as if it had smelled bad. Now we use the macro solution:
#define DISPATCH_BEGIN(func) switch(func) / { #define DISPATCH_FUNCTION(func_c, function) case func_c: / rc = function(para_in,para_out); / break;#define DISPATCH_END(code) default: / rc = code; / }STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out){ STATUS rc; DISPATCH_BEGIN(function_no) DISPATCH_FUNCTION(PROCESSA,ProcessA) DISPATCH_FUNCTION(PROCESSB,ProcessB) DISPATCH_FUNCTION(PROCESSC,ProcessC) ........................ DISPATCH_END(UN_SUPPORT) return rc;}
Well, it's better, but it's better not to go anywhere. It's just to replace multiple lines with one line, and it doesn't change the code's linear growth with the increase in functionality. The reason why I don't like macros is very simple. Currently, there are few (to be honest, I have never seen it) debuggers support macro debugging. This is troublesome. When a piece of macro-doped Code fails to achieve the expected purpose, You have to expand your macros over and over again, to make sure that it is okay (or, you are not sure at all, you can only assume that it is okay ).
Now let's look at the table-driven solution. If the function_no agreed by the system is a continuous linear number or a continuous linear number in a certain range, the driving table can be simply a linear array. Let's look at the example:
Typedef status (* Running) (pvoid para_in, pvoid para_out); typedef struct tagdispatchitem {processfuncptr func_ptr;} running; running condition [failed]; Status driveriocontrol (uint function_no, pvoid para_in, pvoid para_out) {/* or you need to adjust function_no so that its range falls into [0-max_dispatch_item) */If (function_no> = 0) & (function_no <max_dispatch_item )) return dispatch_table [function_no]. func_ptr (para_in, para_out); else return un_support;} If function_no is not linear, you need to look up the table: typedef struct tagdispatchitem {unit func_no; processfuncptr func_ptr;} dispatch_item; dispatch_item dispatch_table [max_dispatch_item]; Status driveriocontrol (uint function_no, pvoid para_in, pvoid para_out) {int I, find =-1; for (I = 0; I <max_dispatch_item; I ++) {If (function_no = dispatch_table [I]. func_no) {return dispatch_table [I]. func_ptr (para_in, para_out) ;}} return un_support ;}
The benefit of using the table driver is that the driveriocontrol Code adds new features in these rows. You only need to maintain the drive table dispatch_table, so that you can get rid of the tedious switch-case.
In the switch-case statement in the previous example, the parameters of each case Branch are relatively simple and neat, that is, all case branches use the same parameters para_in and para_out. What if the parameters used by each branch are not neat? Therefore, encapsulation is required. A unified data structure is usually defined using struct and union as interface parameters, different Branch dispatch functions extract the corresponding data from the Unified Data Structure as needed. The specific method is to first define a unified dispatch function interface, for example:
Typedef status (* processfuncptr) (dispatch_data * para );
Then define dispatch_data in combination with struct and union. dispatch_data usually has the following structure:
Typedef struct tagdispatch_data
{
// Declare the common data of all branches here
Union
{
// Declare the data used by the processa branch here
} Processa;
Union
{
// Declare the data used by the processa branch here
} Processb;
Union
{
// Declare the data used by the processa branch here
} Processc;
......
} Dispatch_data;
Friends who have worked on Windows drivers must be familiar with IRP. IRP is a typical example.
That's it. Finish the job and close the job.
========================================================== ============================================
The following is a comment in the original article: Table-driven may cause efficiency loss and potential problems in drive.
Because function pointer arrays are used in loop queries instead of direct calls, on the one hand, the operation efficiency may be reduced. Every IOCTL call must go through a loop, and the more functions, the slower the call. (if you carefully check the assembly code generated by the switch, in fact, the compiler will optimize it to the look-up table method in this case, but it is more efficient ). on the other hand, some bugs occur only when the compilation times an error to the runtime, resulting in a basket screen. efficiency and Security are what drivers need most.