Follow and star our public account for direct access to exciting content
Source: https://www.cnblogs.com/clover-toeic/p/3730362.html
Author: clover_toeic
Data overwhelms everything. If the correct data structure is chosen and everything is organized neatly, the correct algorithm becomes self-evident. The core of programming is data structure, not algorithms.
——Rob Pike
Description
This article is based on the understanding that data is volatile, while logic is stable. The programming implementations cited in this article are mostly code snippets, but they do not affect the completeness of the description.
Although the programming examples are based on C language, the programming concepts are also applicable to other languages. Furthermore, this article does not involve discussions on language-related runtime efficiency.
1 Concept Introduction
The so-called Table-Driven Approach is simply a method of obtaining data through table lookup. The "table" here is usually an array, but can be seen as a manifestation of a database.
A typical example of the table-driven approach is looking up the pronunciation of an unknown Chinese character using a radical index table, which calculates an index value based on the character's shape and maps it to the corresponding page number. Compared to sequentially flipping through the dictionary page by page, the radical lookup method is highly efficient.
In programming, when the amount of data is small, logical judgment statements (if…else or switch…case) can be used to obtain values; however, as the amount of data increases, the logical statements become longer, and the advantages of the table-driven approach begin to emerge. For example, using base-36 (where A represents 10, B represents 11, etc.) to represent larger numbers, the logical judgment statements are as follows:
if(ucNum < 10){ ucNumChar = ConvertToChar(ucNum);}else if(ucNum == 10){ ucNumChar = 'A';}else if(ucNum == 11){ ucNumChar = 'B';}else if(ucNum == 12){ ucNumChar = 'C';}//... ...else if(ucNum == 35){ ucNumChar = 'Z';}
Of course, a switch…case structure can also be used, but the implementation is very verbose. Using the table-driven approach (storing numChar in an array) is much more intuitive and concise, as shown below:
CHAR aNumChars[] = {'0', '1', '2', /*3~9*/'A', 'B', 'C', /*D~Y*/'Z'};CHAR ucNumChar = aNumChars[ucNum % sizeof(aNumChars)];
This method of directly using the variable as a subscript to read values is known as direct table lookup.
Note that if you are familiar with string operations, the above writing can be made even more concise:
CHAR ucNumChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[ucNum];
When using the table-driven approach, two issues need to be addressed: first, how to look up the table and read the correct data; second, what to store in the table, such as values or function pointers. The former is discussed in section 1.1 "Table Lookup Methods", and the latter is discussed in section 1.2 "Practical Examples".
1.1 Table Lookup Methods
Common table lookup methods include direct lookup, index lookup, and segmented lookup.
1.1.1 Direct Lookup
This means directly accessing data through array subscripts. If you are familiar with hash tables, you can easily see that this table lookup method is essentially the direct access method of a hash table.
For example, to get the name of the day of the week, the logical judgment statements are as follows:
Follow our public account: C Language Chinese Community, and receive 300G of quality programming materials for free.
if(0 == ucDay){ pszDayName = "Sunday";}else if(1 == ucDay){ pszDayName = "Monday";//... ...else if(6 == ucDay){ pszDayName = "Saturday";}
To achieve the same functionality, you can store this data in a table:
CHAR *paNumChars[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};CHAR *pszDayName = paNumChars[ucDay];
Similar to the characteristics of a hash table, the table-driven approach is suitable for situations where there is no need for ordered traversal of data, and the size of the data can be predicted in advance.
For overly complex and large judgments, data can be stored in a file, and the file can be loaded to initialize the array when needed, thus adjusting the values inside without modifying the program.
Sometimes, a key value conversion is required before access. For example, when using the table-driven approach to represent the busy or idle state of ports, the slot port number needs to be mapped to a global number. The generated array of port numbers corresponds to the global port number, and the element values represent the busy or idle state of the corresponding ports.
1.1.2 Index Lookup
Sometimes, a key value conversion alone cannot convert data (such as English words) into key values. In this case, the corresponding relationship of the conversion can be written into an index table, which is known as index access.
For example, if there are 100 items with a 4-digit number ranging from 0000 to 9999, you only need to allocate an array of length 100, corresponding to 2-digit key values. However, converting the 4-digit number into a 2-digit key value may be too complex or irregular, and the best method is to establish an index table that saves this conversion relationship. Using index access saves memory and is easy to maintain. For instance, index A indicates access by name, while index B indicates access by number.
1.1.3 Segmented Lookup
This method determines classification (subscript) by identifying the range of data. Some data can be divided into several intervals, which have a step-like nature, such as score levels. In this case, the upper (or lower) limits of each interval can be stored in one table, and the corresponding values can be stored in another table. The first table determines the segment in which the data falls, and then the corresponding value can be read from the second table using the segment subscript. Note that attention should be paid to endpoints, and binary search can be used. Additionally, consider using index methods as a substitute.
For example, to determine performance levels based on scores:
#define MAX_GRADE_LEVEL (INT8U)5DOUBLE aRangeLimit[MAX_GRADE_LEVEL] = {50.0, 60.0, 70.0, 80.0, 100.0};CHAR *paGrades[MAX_GRADE_LEVEL] = {"Fail", "Pass", "Credit", "Distinction", "High Distinction"};
static CHAR* EvaluateGrade(DOUBLE dScore){ INT8U ucLevel = 0; for(; ucLevel < MAX_GRADE_LEVEL; ucLevel++) { if(dScore < aRangeLimit[ucLevel]) return paGrades[ucLevel]; } return paGrades[0];}
The above two tables (arrays) can also be merged into one table (struct array), as shown below:
typedef struct{ DOUBLE aRangeLimit; CHAR *pszGrade;}T_GRADE_MAP;
T_GRADE_MAP gGradeMap[MAX_GRADE_LEVEL] = { {50.0, "Fail"}, {60.0, "Pass"}, {70.0, "Credit"}, {80.0, "Distinction"}, {100.0, "High Distinction"}};
static CHAR* EvaluateGrade(DOUBLE dScore){ INT8U ucLevel = 0; for(; ucLevel < MAX_GRADE_LEVEL; ucLevel++) { if(dScore < gGradeMap[ucLevel].aRangeLimit) return gGradeMap[ucLevel].pszGrade; } return gGradeMap[0].pszGrade;}
This table structure has the rudiments of a database and can be expanded to support more complex data. The lookup method is usually index lookup, and occasionally segmented lookup; when the index has regularity (such as consecutive integers), it degenerates into direct lookup.
When using the segmented lookup method, attention should be paid to boundaries, ensuring that the upper limit of each segment is included. Identify all values that do not fall within the highest level range, and then assign all remaining values to the highest level. Sometimes, it is necessary to artificially add an upper limit to the highest level range.
Also, be careful not to mistakenly use "<" instead of "<= ". Ensure that the loop ends appropriately after identifying values that belong to the highest level range, and also ensure proper handling of range boundaries.
1.2 Practical Examples
Most examples in this section are taken from actual projects. The table forms include one-dimensional arrays, two-dimensional arrays, and struct arrays; the table contents include data, strings, and function pointers. Based on the table-driven concept, the table forms and contents can yield rich combinations.
1.2.1 Character Statistics
Problem: Count the occurrences of each digit in a string of numbers input by the user.
The main code for the ordinary solution is as follows:
INT32U aDigitCharNum[10] = {0}; /* Count of each digit character in the input string */INT32U dwStrLen = strlen(szDigits);
INT32U dwStrIdx = 0;for(; dwStrIdx < dwStrLen; dwStrIdx++){ switch(szDigits[dwStrIdx]) { case '1': aDigitCharNum[0]++; break; case '2': aDigitCharNum[1]++; break; //... ... case '9': aDigitCharNum[8]++; break; }}
The shortcomings of this solution are obvious; it is neither elegant nor flexible. The key issue is that it does not directly associate the digit characters with the subscripts of the array aDigitCharNum.
The following shows a more concise implementation:
for(; dwStrIdx < dwStrLen; dwStrIdx++){ aDigitCharNum[szDigits[dwStrIdx] - '0']++;}
This implementation considers that 0 is also a digit character. This solution can also be extended to count all visible ASCII characters.
1.2.2 Month-Day Validation
Problem: Validate the number of days for a given year and month (distinguishing between leap years and common years).
The main code for the ordinary solution is as follows:
switch(OnuTime.Month){ case 1: case 3: case 5: case 7: case 8: case 10: case 12: if(OnuTime.Day>31 || OnuTime.Day<1) { CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~31)!!!\n", OnuTime.Day); retcode = S_ERROR; } break; case 2: if(((OnuTime.Year%4 == 0) && (OnuTime.Year%100 != 0)) || (OnuTime.Year%400 == 0)) { if(OnuTime.Day>29 || OnuTime.Day<1) { CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~29)!!!\n", OnuTime.Day); retcode = S_ERROR; } } else { if(OnuTime.Day>28 || OnuTime.Day<1) { CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~28)!!!\n", OnuTime.Day); retcode = S_ERROR; } } break; case 4: case 6: case 9: case 11: if(OnuTime.Day>30 || OnuTime.Day<1) { CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~30)!!!\n", OnuTime.Day); retcode = S_ERROR; } break; default: CtcOamLog(FUNCTION_Pon,"Don't support this Month: %d(1~12)!!!\n", OnuTime.Month); retcode = S_ERROR; break;}
The following shows a more concise implementation:
#define MONTH_OF_YEAR 12 /* Number of months in a year */#define IS_LEAP_YEAR(year) ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0))
/* Number of days in each month of a common year, subscript corresponds to the month */INT8U aDayOfCommonMonth[MONTH_OF_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};INT8U ucMaxDay = 0;if((OnuTime.Month == 2) && (IS_LEAP_YEAR(OnuTime.Year))) ucMaxDay = aDayOfCommonMonth[1] + 1;else ucMaxDay = aDayOfCommonMonth[OnuTime.Month-1];if((OnuTime.Day < 1) || (OnuTime.Day > ucMaxDay){ CtcOamLog(FUNCTION_Pon,"Month %d doesn't have this Day: %d(1~%d)!!!\n", OnuTime.Month, OnuTime.Day, ucMaxDay); retcode = S_ERROR;}
1.2.3 Name Construction
Problem: Construct a business type name string based on the business type carried by the WAN interface (Bitmap).
The main code for the ordinary solution is as follows:
void Sub_SetServerType(INT8U *ServerType, INT16U wan_servertype){ if ((wan_servertype && 0x0001) == 0x0001) { strcat(ServerType, "_INTERNET"); } if ((wan_servertype && 0x0002) == 0x0002) { strcat(ServerType, "_TR069"); } if ((wan_servertype && 0x0004) == 0x0004) { strcat(ServerType, "_VOIP"); } if ((wan_servertype && 0x0008) == 0x0008) { strcat(ServerType, "_OTHER"); }}
The following shows a more concise implementation in C language:
/* Get the bit of var variable, numbered from right to left */#define GET_BIT(var, bit) (((var) >> (bit)) && 0x1)const CHAR* paSvrNames[] = {"_INTERNET", "_TR069", "_VOIP", "_OTHER"};const INT8U ucSvrNameNum = sizeof(paSvrNames) / sizeof(paSvrNames[0]);
VOID SetServerType(CHAR *pszSvrType, INT16U wSvrType){ INT8U ucIdx = 0; for(; ucIdx < ucSvrNameNum; ucIdx++) { if(1 == GET_BIT(wSvrType, ucIdx)) strcat(pszSvrType, paSvrNames[ucIdx]); }}
The new implementation separates data from logic, making maintenance very convenient. As long as the logic (rules) remains unchanged, the only possible modification is the data (paSvrNames).
1.2.4 Value Name Parsing
Problem: Output the corresponding string based on the value of an enumeration variable, such as PORT_FE(1) outputs "Fe".
//Value name mapping table structure definition, used for value parsertypedef struct{ INT32U dwElem; //Value to be parsed, usually an enumeration variable CHAR* pszName; //Pointer to the string corresponding to the parsed value}T_NAME_PARSER;
/******************************************************************************* Function Name: NameParser* Function Description: Value parser, converts the given value to the corresponding named string* Input Parameters: VOID *pvMap : Value name mapping table array, containing elements of T_NAME_PARSER structure type The void pointer allows users to customize more meaningful structure names and/or member names while keeping the number and type of members unchanged. INT32U dwEntryNum : Number of entries in the value name mapping table array INT32U dwElem : Value to be parsed, usually an enumeration variable INT8U* pszDefName : Pointer to the default named string, can be NULL* Output Parameters: NA* Return Value : INT8U *: Named string corresponding to the value When the given value cannot be parsed, if pszDefName is NULL, return the hexadecimal format string corresponding to the value; otherwise, return pszDefName.******************************************************************************/INT8U *NameParser(VOID *pvMap, INT32U dwEntryNum, INT32U dwElem, INT8U* pszDefName){ CHECK_SINGLE_POINTER(pvMap, "NullPointer");
INT32U dwEntryIdx = 0; for(dwEntryIdx = 0; dwEntryIdx < dwEntryNum; dwEntryIdx++) { T_NAME_PARSER *ptNameParser = (T_NAME_PARSER *)pvMap; if(dwElem == ptNameParser->dwElem) { return ptNameParser->pszName; } //The ANSI standard prohibits algorithm operations on void pointers; the GNU standard specifies that void* algorithm operations are consistent with char*. //For portability, you can change the type of pvMap to INT8U*, or define a local variable of type INT8U* pointing to pvMap.
pvMap += sizeof(T_NAME_PARSER); }
if(NULL != pszDefName) { return pszDefName; } else { static INT8U szName[12] = {0}; //Max:"0xFFFFFFFF" sprintf(szName, "0x%X", dwElem); return szName; }}
The following gives a simple application example of NameParser://UNI port type value name mapping table structure definitiontypedef struct{ INT32U dwPortType; INT8U* pszPortName;}T_PORT_NAME;//UNI port type parserT_PORT_NAME gUniNameMap[] = { {1, "Fe"}, {3, "Pots"}, {99, "Vuni"}};const INT32U UNI_NAM_MAP_NUM = (INT32U)(sizeof(gUniNameMap)/sizeof(T_PORT_NAME));VOID NameParserTest(VOID){ INT8U ucTestIndex = 1;
printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("Unknown", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0, "Unknown")) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("DefName", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0, "DefName")) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("Fe", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 1, "Unknown")) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("Pots", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 3, "Unknown")) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("Vuni", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 99, NULL)) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("Unknown", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 255, "Unknown")) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("0xABCD", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0xABCD, NULL)) ? "ERROR" : "OK"); printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, strcmp("NullPointer", NameParser(NULL, UNI_NAM_MAP_NUM, 0xABCD, NULL)) ? "ERROR" : "OK");}
The gUniNameMap in actual projects has more than a dozen entries, and if a logical chain were used, it would be very verbose.
1.2.5 Value Mapping
Problem: The same parameter enumeration values may differ between different modules and need to be adapted.
Here, instead of providing the ordinary switch…case or if…else if…else structure, the following table-driven implementation is directly shown:
typedef struct{ PORTSTATE loopMEState; PORTSTATE loopMIBState;}LOOPMAPSTRUCT;
static LOOPMAPSTRUCT s_CesLoop[] = { {NO_LOOP, e_ds1_looptype_noloop}, {PAYLOAD_LOOP, e_ds1_looptype_PayloadLoop}, {LINE_LOOP, e_ds1_looptype_LineLoop}, {PON_LOOP, e_ds1_looptype_OtherLoop}, {CES_LOOP, e_ds1_looptype_InwardLoop}};
PORTSTATE ConvertLoopMEStateToMIBState(PORTSTATE vPortState){ INT32U num = 0, ii;
num = ARRAY_NUM(s_CesLoop); for(ii = 0; ii < num; ii++) { if(vPortState == s_CesLoop[ii].loopMEState) return s_CesLoop[ii].loopMIBState; } return e_ds1_looptype_noloop;}
Correspondingly, to map from loopMIBState to loopMEState, a ConvertLoopMIBStateToMEState function needs to be defined. Furthermore, all similar one-to-one mapping relationships must have such mapping (conversion) functions, which can be quite cumbersome.
In fact, from an abstract perspective, this mapping relationship is very simple. By extracting commonalities, a parameterized macro can be defined, as shown below:
/*********************************************************** Function Description: Perform one-to-one mapping of a two-dimensional array mapping table for parameter adaptation* Parameter Description: map -- Two-dimensional array mapping table elemSrc -- Mapping source, i.e., the element value to be mapped elemDest -- Mapping result corresponding to the mapping source direction -- Mapping direction byte, indicating which column to map from the array to which column. The high 4 bits correspond to the mapping source column, and the low 4 bits correspond to the mapping result column. defaultVal -- Set the mapping result to the default value in case of mapping failure* Example: ARRAY_MAPPER(gCesLoopMap, 3, ucLoop, 0x10, NO_LOOP); Then ucLoop = 2(LINE_LOOP)**********************************************************/#define ARRAY_MAPPER(map, elemSrc, elemDest, direction, defaultVal) do{
INT8U ucMapIdx = 0, ucMapNum = 0;
ucMapNum = sizeof(map)/sizeof(map[0]);
for(ucMapIdx = 0; ucMapIdx < ucMapNum; ucMapIdx++)
{
if((elemSrc) == map[ucMapIdx][((direction)&&0xF0)>>4])
{
elemDest = map[ucMapIdx][(direction)&&0x0F];
break;
}
}
if(ucMapIdx == ucMapNum)
{
elemDest = (defaultVal);
}
}while(0)
When converting parameter values, directly call the unified mapper macro, as shown below:
static INT8U gCesLoopMap[][2] = { {NO_LOOP, e_ds1_looptype_noloop}, {PAYLOAD_LOOP, e_ds1_looptype_PayloadLoop}, {LINE_LOOP, e_ds1_looptype_LineLoop}, {PON_LOOP, e_ds1_looptype_OtherLoop}, {CES_LOOP, e_ds1_looptype_InwardLoop}};
ARRAY_MAPPER(gCesLoopMap, tPara.dwParaVal[0], dwLoopConf, 0x01, e_ds1_looptype_noloop);
Another example:
#define CES_DEFAULT_JITTERBUF (INT32U)2000 /* Default jitter buffer is 2000us, while 1 frame = 125us */#define CES_JITTERBUF_STEP (INT32U)125 /* Jitter buffer step size is 125us, i.e., 1 frame */#define CES_DEFAULT_QUEUESIZE (INT32U)5#define CES_DEFAULT_MAX_QUEUESIZE (INT32U)7
#define ARRAY_NUM(array) (sizeof(array) / sizeof((array)[0])) /* Number of array elements */typedef struct{ INT32U dwJitterBuffer; INT32U dwFramePerPkt; INT32U dwQueueSize;}QUEUE_SIZE_MAP;/* gCesQueueSizeMap can also use (JitterBuffer / FramePerPkt) value as an index, making it more compact */static QUEUE_SIZE_MAP gCesQueueSizeMap[]= { {1,1,1}, {1,2,1}, {2,1,2}, {2,2,1}, {3,1,3}, {3,2,1}, {4,1,3}, {4,2,1}, {5,1,4}, {5,2,3}, {6,1,4}, {6,2,3}, {7,1,4}, {7,2,3}, {8,1,4}, {8,2,3}, {9,1,5}, {9,2,4}, {10,1,5}, {10,2,4}, {11,1,5}, {11,2,4}, {12,1,5}, {12,2,4}, {13,1,5}, {13,2,4}, {14,1,5}, {14,2,4}, {15,1,5}, {15,2,4}, {16,1,5}, {16,2,4}, {17,1,6}, {17,2,5}, {18,1,6}, {18,2,5}, {19,1,6}, {19,2,5}, {20,1,6}, {20,2,5}, {21,1,6}, {21,2,5}, {22,1,6}, {22,2,5}, {23,1,6}, {23,2,5}, {24,1,6}, {24,2,5}, {25,1,6}, {25,2,5}, {26,1,6}, {26,2,5}, {27,1,6}, {27,2,5}, {28,1,6}, {28,2,5}, {29,1,6}, {29,2,5}, {30,1,6}, {30,2,5}, {31,1,6}, {31,2,5}, {32,1,6}, {32,2,5}};/*********************************************************** Function Name: CalcQueueSize* Function Description: Calculate QueueSize based on JitterBuffer and FramePerPkt* Note: The configured maximum buffer depth* = 2 * JitterBuffer / FramePerPkt* = 2 * N Packet = 2 ^ QueueSize* JitterBuffer is a multiple of 125us frame rate,* FramePerPkt is the number of frames per packet,* QueueSize is rounded up, with a maximum of 7.**********************************************************/INT32U CalcQueueSize(INT32U dwJitterBuffer, INT32U dwFramePerPkt){ INT8U ucIdx = 0, ucNum = 0;
//This function currently only considers E1 ucNum = ARRAY_NUM(gCesQueueSizeMap); for(ucIdx = 0; ucIdx < ucNum; ucIdx++) { if((dwJitterBuffer == gCesQueueSizeMap[ucIdx].dwJitterBuffer) && (dwFramePerPkt == gCesQueueSizeMap[ucIdx].dwFramePerPkt)) { return gCesQueueSizeMap[ucIdx].dwQueueSize; } }
return CES_DEFAULT_MAX_QUEUESIZE;}
1.2.6 Version Control
Problem: Control version negotiation between OLT and ONU. The ONU local setting has a three-bit control word, where bit2 (MSB) to bit0 (LSB) correspond to version numbers 0x21, 0x30, and 0xAA; and bitX being 0 indicates reporting the corresponding version number, while bitX being 1 indicates not reporting the corresponding version number. Other version numbers such as 0x20, 0x13, and 0x1 must be reported, i.e., they are not controlled.
The initial implementation used an if…else if…else structure, resulting in very verbose code, as shown below:
pstSendTlv->ucLength = 0x1f;if (gOamCtrlCode == 0){ vosMemCpy(pstSendTlv->aucVersionList, ctc_oui, 3); pstSendTlv->aucVersionList[3] = 0x30; vosMemCpy(&&(pstSendTlv->aucVersionList[4]), ctc_oui, 3); pstSendTlv->aucVersionList[7] = 0x21; vosMemCpy(&&(pstSendTlv->aucVersionList[8]), ctc_oui, 3); pstSendTlv->aucVersionList[11] = 0x20; vosMemCpy(&&(pstSendTlv->aucVersionList[12]), ctc_oui, 3); pstSendTlv->aucVersionList[15] = 0x13; vosMemCpy(&&(pstSendTlv->aucVersionList[16]), ctc_oui, 3); pstSendTlv->aucVersionList[19] = 0x01; vosMemCpy(&&(pstSendTlv->aucVersionList[20]), ctc_oui, 3); pstSendTlv->aucVersionList[23] = 0xaa;}else if (gOamCtrlCode == 1){ vosMemCpy(pstSendTlv->aucVersionList, ctc_oui, 3); pstSendTlv->aucVersionList[3] = 0x30; vosMemCpy(&&(pstSendTlv->aucVersionList[4]), ctc_oui, 3); pstSendTlv->aucVersionList[7] = 0x21; vosMemCpy(&&(pstSendTlv->aucVersionList[8]), ctc_oui, 3); pstSendTlv->aucVersionList[11] = 0x20; vosMemCpy(&&(pstSendTlv->aucVersionList[12]), ctc_oui, 3); pstSendTlv->aucVersionList[15] = 0x13; vosMemCpy(&&(pstSendTlv->aucVersionList[16]), ctc_oui, 3); pstSendTlv->aucVersionList[19] = 0x01;}//The handling code for gOamCtrlCode == 2~6 is omittedelse if (gOamCtrlCode == 7){ vosMemCpy(&&(pstSendTlv->aucVersionList), ctc_oui, 3); pstSendTlv->aucVersionList[3] = 0x20; vosMemCpy(&&(pstSendTlv->aucVersionList[4]), ctc_oui, 3); pstSendTlv->aucVersionList[7] = 0x13; vosMemCpy(&&(pstSendTlv->aucVersionList[8]), ctc_oui, 3); pstSendTlv->aucVersionList[11] = 0x01;}
The following shows a more concise implementation in C language (based on a two-dimensional array):
/*********************************************************************** Version control word array definition* gOamCtrlCode: Bitmap control word. Bit-X is 0 to report the corresponding version, Bit-X is 1 to mask the corresponding version.* CTRL_VERS_NUM: Number of controllable versions.* CTRL_CODE_NUM: Number of control words. Related to CTRL_VERS_NUM.* gOamVerCtrlMap: Version control word array. Rows correspond to control words, columns correspond to controllable versions. Element values of 0 indicate not reporting the corresponding version, while non-zero element values indicate reporting that element value.* Note: This array aims to achieve "data and control isolation". If you want to add controllable versions later, you only need to modify -- CTRL_VERS_NUM -- gOamVerCtrlMap to add new rows (control words) -- gOamVerCtrlMap to add new columns (controllable versions)**********************************************************************/#define CTRL_VERS_NUM 3#define CTRL_CODE_NUM (1<<CTRL_VERS_NUM)u8_t gOamVerCtrlMap[CTRL_CODE_NUM][CTRL_VERS_NUM] = { /* Ver21 Ver30 VerAA */ {0x21, 0x30, 0xaa}, /*gOamCtrlCode = 0*/ {0x21, 0x30, 0 }, /*gOamCtrlCode = 1*/ {0x21, 0, 0xaa}, /*gOamCtrlCode = 2*/ {0x21, 0, 0 }, /*gOamCtrlCode = 3*/ { 0, 0x30, 0xaa}, /*gOamCtrlCode = 4*/ { 0, 0x30, 0 }, /*gOamCtrlCode = 5*/ { 0, 0, 0xaa}, /*gOamCtrlCode = 6*/ { 0, 0, 0 } /*gOamCtrlCode = 7*/};#define INFO_TYPE_VERS_LEN 7 /* InfoType + Length + OUI + ExtSupport + Version */
u8_t verIdx = 0;u8_t index = 0;for(verIdx = 0; verIdx < CTRL_VERS_NUM; verIdx++){ if(gOamVerCtrlMap[gOamCtrlCode][verIdx] != 0) { vosMemCpy(&&pstSendTlv->aucVersionList[index], ctc_oui, 3); index += 3; pstSendTlv->aucVersionList[index++] = gOamVerCtrlMap[gOamCtrlCode][verIdx]; }}vosMemCpy(&&pstSendTlv->aucVersionList[index], ctc_oui, 3);index += 3;pstSendTlv->aucVersionList[index++] = 0x20;vosMemCpy(&&pstSendTlv->aucVersionList[index], ctc_oui, 3);index += 3;pstSendTlv->aucVersionList[index++] = 0x13;vosMemCpy(&&pstSendTlv->aucVersionList[index], ctc_oui, 3);index += 3;pstSendTlv->aucVersionList[index++] = 0x01;
pstSendTlv->ucLength = INFO_TYPE_VERS_LEN + index;
1.2.7 Message Processing
Problem: The terminal inputs different print commands, calling the corresponding print functions to control different levels of printing. This is an event-driven program. This module receives messages sent by other modules (such as serial port drivers) and calls different functions for processing based on the print level string and switch mode in the message. A common implementation method is as follows:
void logall(void){ g_log_control[0] = 0xFFFFFFFF;}
void noanylog(void){ g_log_control[0] = 0;}
void logOam(void){ g_log_control[0] |= (0x01 << FUNCTION_Oam);}void nologOam(void){ g_log_control[0] &= ~(0x01 << FUNCTION_Oam);}//... ...void logExec(char *name, INT8U enable){ CtcOamLog(FUNCTION_Oam,"log %s %d\n",name,enable); if (enable == 1) /*log*/ { if (strcasecmp(name,"all") == 0) { /*String comparison, case insensitive*/ logall(); } else if (strcasecmp(name,"oam") == 0) { logOam(); } else if (strcasecmp(name,"pon") == 0) { logPon(); //... ... } else if (strcasecmp(name,"version") == 0) { logVersion(); } else if (enable == 0) /*nolog*/ { if (strcasecmp(name,"all") == 0) { noanylog(); } else if (strcasecmp(name,"oam") == 0) { nologOam(); } else if (strcasecmp(name,"pon") == 0) { nologPon(); //... ... } else if (strcasecmp(name,"version") == 0) { nologVersion(); } else { printf("bad log para\n"); }}
The following shows a more concise implementation in C language:
typedef struct{ OAM_LOG_OFF = (INT8U)0, OAM_LOG_ON = (INT8U)1}E_OAM_LOG_MODE;typedef FUNC_STATUS (*OamLogHandler)(VOID);typedef struct{ CHAR *pszLogCls; /* Print level */ E_OAM_LOG_MODE eLogMode; /* Print mode */ OamLogHandler fnLogHandler; /* Print function */}T_OAM_LOG_MAP;
T_OAM_LOG_MAP gOamLogMap[] = { {"all", OAM_LOG_OFF, noanylog}, {"oam", OAM_LOG_OFF, nologOam}, //... ... {"version", OAM_LOG_OFF, nologVersion},
{"all", OAM_LOG_ON, logall}, {"oam", OAM_LOG_ON, logOam}, //... ... {"version", OAM_LOG_ON, logVersion}};
INT32U gOamLogMapNum = sizeof(gOamLogMap) / sizeof(T_OAM_LOG_MAP);
VOID logExec(CHAR *pszName, INT8U ucSwitch){ INT8U ucIdx = 0; for(; ucIdx < gOamLogMapNum; ucIdx++) { if((ucSwitch == gOamLogMap[ucIdx].eLogMode) && (!strcasecmp(pszName, gOamLogMap[ucIdx].pszLogCls)); { gOamLogMap[ucIdx].fnLogHandler(); return; } } if(ucIdx == gOamLogMapNum) { printf("Unknown LogClass(%s) or LogMode(%d)!\n", pszName, ucSwitch); return; }}
The advantages of this table-driven message processing implementation are as follows:
1. Enhanced readability, how messages are processed is clear from the table.
2. Enhanced extensibility. It is easier to modify; to add new messages, you only need to modify the data, not the flow.
3. Reduced complexity. By transferring the complexity of program logic to data that is easier for humans to handle, the goal of controlling complexity is achieved.
4. Clear main structure, code reuse.
If each index is a sequential enumeration value, a multi-dimensional array can be established (each dimension corresponds to an index), and the processing function can be directly located based on the subscript, which would be more efficient.
Note that considering that in this section's examples, functions like logOam/logPon or nologOam/nologPon are essentially based on bit operations of print levels, further simplification can be made. The following is an example of a similar implementation:
/* Log control type definition */typedef enum{ LOG_NORM = 0, /* Unclassified log, can be used for general logs */ LOG_FRM, /* Frame, OMCI frame log */ LOG_PON, /* Pon, optical link related log */ LOG_ETH, /* Ethernet, Layer2 Ethernet log */ LOG_NET, /* Internet, Layer3 network log */ LOG_MULT, /* Multicast, multicast log */ LOG_QOS, /* QOS, traffic log */ LOG_CES, /* Ces, TDM circuit emulation log */ LOG_VOIP, /* Voip, voice log */ LOG_ALM, /* Alarm, alarm log */ LOG_PERF, /* Performance, performance statistics log */ LOG_VER, /* Version, software upgrade log */ LOG_XDSL, /* xDsl log */ LOG_DB, /* Database operation log */ //New log types can be extended here, supporting a total of 32 log types LOG_ALL = UINT_MAX /* All log types */}E_LOG_TYPE;
/***************************************************************************** * Variable Name: gOmciLogCtrl * Description: OMCI log control word, Bitmap format (bit number from LSB to MSB sequentially from Bit0->BitN). * Bit0~N correspond to each enumeration value of E_LOG_TYPE (except LOG_ALL). * BitX is 0 to turn off the log function corresponding to the log type, BitX is 1 to turn on the log function corresponding to the log type. * Variable Range: This variable is a static global variable of four-byte integer type, supporting 32 log types. * Access Description: Access/set control word through GetOmciLogCtrl/SetOmciLogCtrl/OmciLogCtrl function. *****************************************************************************/static INT32U gOmciLogCtrl = 0;
//Log type string array, subscript corresponds to the enumeration value of each string.static const INT8U* paLogTypeName[] = { "Norm", "Frame", "Pon", "Ethernet", "Internet", "Multicast", "Qos", "Ces", "Voip", "Alarm", "Performance", "Version", "Xdsl", "Db"};static const INT8U ucLogTypeNameNum = sizeof(paLogTypeName) / sizeof(paLogTypeName[0]);
static VOID SetGlobalLogCtrl(E_LOG_TYPE eLogType, INT8U ucLogSwitch){ if(LOG_ON == ucLogSwitch) gOmciLogCtrl = LOG_ALL; else gOmciLogCtrl = 0;}
static VOID SetSpecificLogCtrl(E_LOG_TYPE eLogType, INT8U ucLogSwitch){ if(LOG_ON == ucLogSwitch) SET_BIT(gOmciLogCtrl, eLogType); else CLR_BIT(gOmciLogCtrl, eLogType);}
VOID OmciLogCtrl(CHAR *pszLogType, INT8U ucLogSwitch){ if(0 == strncasecmp(pszLogType, "All", LOG_TYPE_CMP_LEN)) { SetGlobalLogCtrl(LOG_ALL, ucLogSwitch); return; }
INT8U ucIdx = 0; for(ucIdx = 0; ucIdx < ucLogTypeNameNum; ucIdx++) { if(0 == strncasecmp(pszLogType, paLogTypeName[ucIdx], LOG_TYPE_CMP_LEN)) { SetSpecificLogCtrl(ucIdx, ucLogSwitch); printf("LogType: %s, LogSwitch: %s\n", paLogTypeName[ucIdx], (1==ucLogSwitch)?"On":"Off"); return; } }
OmciLogHelp();}
2 Programming Philosophy
The table-driven method is a type of data-driven programming, and its core idea is elaborated in both "The Art of Unix Programming" and "Code Complete 2". Both emphasize that humans find it easier to read complex data structures than complex control flows; that is, compared to program logic, humans are better at handling data.
This section will elaborate on the "Separation Principle" and "Representation Principle" from Unix design principles.
Separation Principle: Separate strategy from mechanism, and interface from engine
Mechanism refers to the functionality provided; strategy refers to how to use the functionality.
Changes in strategy occur much more frequently than changes in mechanism. By separating the two, the mechanism can remain relatively stable while supporting changes in strategy.
"Isolating changes" is mentioned in "Code Complete", as well as in design patterns that separate easily changeable parts from those that are not.
Representation Principle: Embed knowledge into data to achieve simple and robust logic
Even the simplest program logic is difficult for humans to verify, but even very complex data is relatively easy for humans to deduce and model. Data is easier to manage than programming logic. When faced with complex data and complex code, prefer the former. Furthermore, in design, actively transfer the complexity of code into data (refer to "Version Control").
In the "Message Processing" example, the logic for processing each message remains unchanged, but the messages may vary. By separating easily changeable messages from the less changeable lookup logic, we achieve "isolation of changes". Additionally, this example also reflects the separation of internal processing logic (mechanism) and different message processing (strategy).
Data-driven programming can be applied to:
1. Function-level design, as shown in this article.
2. Program-level design, such as implementing state machines using the table-driven method.
3. System-level design, such as DSL.
Note that data-driven programming is not a brand new programming model, but rather a design philosophy that is widely applied in the Unix/Linux open-source community. In data-driven programming, data not only represents the state of an object but also defines the flow of the program, which differs from the "encapsulation" of data in object-oriented design.
3 Appendix
3.1 Opinions from Netizens
(The following opinions are excerpted from a comment by a netizen "Seven Hearts Sunflower" on Blog Park, which are very enlightening.)
In Booch's "Object-Oriented Analysis and Design", it is mentioned that all programming languages have three sources: structured programming; object-oriented programming; and data-driven programming.
I believe the essence of data-driven programming is the idea of "parameterized abstraction", which differs from the "normalized abstraction" idea of OO.
Data-driven programming is commonly used in network game development, but few people specifically mention this term.
Data-driven programming has many names: metaprogramming, interpreter/virtual machine, LOP/microlanguage/DSL, etc. Including declarative programming, markup languages, and even WYSIWYG drag-and-drop controls, all can be considered a form of data-driven programming.
Data-driven programming can help manage complexity and is compatible with both structured programming and OO (orthogonal perspective). By separating the variable and invariant parts, separating strategy and mechanism, this leads to thoughts such as: (separation of data and code, separation of micro-languages and interpreters, separation of generated code and code generators); further: (microkernel plugin architecture).
Metaprogramming can be said to be a more generalized form of data-driven programming. Metaprogramming does not add an indirect layer but retreats one step, making the current layer an indirect layer. Metaprogramming is divided into static metaprogramming (compile-time) and dynamic metaprogramming (runtime). Static metaprogramming is essentially a code generation technique or compiler technology; dynamic metaprogramming is generally implemented through interpreters (or virtual machines).
Data-driven programming should not be considered "anti-abstraction", but it is indeed distinctly different from the thinking mode of "OO abstraction", as described in the book TAOUP: "There is a tense opposition between the modular tradition of Unix and the usage patterns developed around OO languages". It can be said that the idea of data-driven programming is orthogonal to both structured programming and OO, more like a method of "jumping out of the three realms, not being in the five elements".
Programming and the relationship with humans
The limitations of human cognition, behind everything, are based on human factors:
a The number of pieces of information a person can focus on at the same time: 7±2 (hence the need for modularization)
b The average time for a person to receive a new piece of information: 5s (hence the need for simplicity, the total number of system modules should not be too many)
c The intuitiveness of human thinking (human visual ability and fuzzy thinking ability), which means these two points:
A "Straight"—better at thinking about things they can directly touch and play with; (hence the need for "shallow, flat, and transparent" designs, aiming for a design where the code has only straightforward processes),
B "View"—better at observing diagrams rather than deducing logic; (hence the need for table-driven methods, data-driven programming, UML, and visual programming—of course, MDA is too idealistic)
d Humans cannot maintain sustained attention (the proportion of bugs generated by a person in a certain number of lines of code is constant, so languages must be expressive and reflect the economy of expression)
Thus, the need for separation of mechanism and strategy, separation of data and code (data-driven programming), need for micro-languages, need for DSL, need for LOP…
e Humans have a desire for creation and a sense of practical interests (as long as the cost is bearable, they may not always follow norms or want to create norms for profit—especially in hardware fields)
Additionally, a humorous remark: the English abbreviation of "The Art of Unix Programming" is TAOUP, which I think can be understood as UP's TAO—The Way of Throwing Up—throwing complex and volatile logic as data or higher-level code to the upper layer!
3.2 Function Pointers
The function pointers in the "Message Processing" section example have a bit of a plugin structure. These plugins can be easily replaced, added, or deleted, thus changing the behavior of the program. This change is also isolated from the lookup of event handling functions (isolation of changes).
Function pointers are very useful, but care must be taken when using them due to their drawbacks: they cannot check the types of parameters and return values. Because functions have degenerated into pointers, and pointers do not carry type information. The lack of type checking can lead to serious errors when parameters or return values are inconsistent.
For example, defining three functions, each with two parameters:
int max(int x, int y) { return x>y?x:y; }int min(int x, int y) { return x<y?x:y; }int add(int x, int y) { return x+y; }
While the processing function is defined as: int process(int x, int y, int (*f)()) { return (*f)(x, y); }
Where the third parameter is a function pointer that has no parameters and returns an int type variable. But later, if process(a,b,max) is called, where max has two parameters. If the compiler does not catch the error and you accidentally write return (*f)(x,y); as return (*f)(x);, the consequences could be severe.
Therefore, when using function pointers in C language, be cautious of the "type trap".
Copyright Notice: This article is sourced from the internet, freely conveying knowledge, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧
Follow my public account to receive 300G of programming materials for free.
Click "Read the original" for more sharing, welcome to share, collect, like, and look at it.