• 分享一个面向对象的C语言框架

    以下是GObject的一些核心概念和使用方法。 源码:https://gitlab.gnome.org/GNOME/glib/ 教程:https://docs.gtk.org/gobject/index.html 1. GObject的核心概念 动态类型系统:GObject允许程序在运行时进行类型注册,这意味着可以使用纯C语言设计一整套面向对象的软件模块。 内存管理:GObject实现了基于引用计数的内存管理,这简化了内存管理的复杂性。 属性系统:GObject提供了通用的set/get属性获取方法,使得属性管理变得更加简单。 信号机制:GObject内置了简单易用的信号机制,允许对象之间进行通信。 2. GObject的使用示例 在GObject中,类和实例是两个结构体的组合。类结构体初始化函数一般被调用一次,而实例结构体的初始化函数的调用次数等于对象实例化的次数。 所有实例共享的数据保存在类结构体中,而对象私有的数据保存在实例结构体中。 GObject实例的结构体定义如下: typedef struct _GObject GObject; struct _GObject { GTypeInstance  g_type_instance; /*< private >*/ guint          ref_count; /* (atomic) */ GData         *qdata; }; GObject类的结构体定义如下: struct _GObjectClass { GTypeClass   g_type_class; /*< private >*/ GSList      *construct_properties; /*< public >*/ /* seldom overridden */ GObject*   (*constructor)     (GType                  type,                                  guint                  n_construct_properties,                                  GObjectConstructParam *construct_properties); /* overridable methods */ void (*set_property)        (GObject        *object,                                          guint           property_id, const GValue   *value,                                          GParamSpec     *pspec); void (*get_property)        (GObject        *object,                                          guint           property_id,                                          GValue         *value,                                          GParamSpec     *pspec); void (*dispose)         (GObject        *object); void (*finalize)        (GObject        *object); /* seldom overridden */ void (*dispatch_properties_changed) (GObject      *object,                          guint     n_pspecs,                          GParamSpec  **pspecs); /* signals */ void (*notify)          (GObject    *object,                      GParamSpec *pspec); /* called when done constructing */ void (*constructed)     (GObject    *object); /*< private >*/ gsize     flags;   gsize         n_construct_properties;   gpointer pspecs;   gsize n_pspecs; /* padding */ gpointer  pdummy[3]; }; 以下是一个简单的示例,展示了如何创建和使用GObject实例: #include int main (int argc, char **argv) {     GObject* instance1, *instance2; // 指向实例的指针 GObjectClass* class1, *class2; // 指向类的指针 instance1 = g_object_new (G_TYPE_OBJECT, NULL);     instance2 = g_object_new (G_TYPE_OBJECT, NULL);     g_print ("The address of instance1 is %p\n", instance1);     g_print ("The address of instance2 is %p\n", instance2);     class1 = G_OBJECT_GET_CLASS (instance1);     class2 = G_OBJECT_GET_CLASS (instance2);     g_print ("The address of the class of instance1 is %p\n", class1);     g_print ("The address of the class of instance2 is %p\n", class2);     g_object_unref (instance1);     g_object_unref (instance2); return 0; } The address of instance1 is 0x55fb9141ad20 The address of instance2 is 0x55fb9141ad40 The address of the class of instance1 is 0x55fb9141a350 The address of the class of instance2 is 0x55fb9141a350 在这个示例中,g_object_new函数用于创建GObject实例,并返回指向它的指针。 G_TYPE_OBJECT是GObject基类的类型标识符,所有其他GObject类型都从这个基类型派生。 宏G_OBJECT_GET_CLASS返回指向参数所属类变量的指针。g_object_unref用于销毁实例变量并释放内存。 实例1与实例2的存储空间是不同的,每个实例都有自己的空间。 两个类的存储空间是相同的,两个GObject实例共享同一个类。 3. GObject的信号机制 GObject允许定义和使用属性,以及发出和连接信号。 这些特性使得GObject非常适合用于构建复杂的软件系统,尤其是在需要组件间通信和属性管理的场景中。 信号最基本的用途是实现事件通知。例如:创建一个信号,当调用文件写方法时,触发文件变化信号。 创建信号: file_signals[CHANGED] =    g_signal_newv ("changed",                  G_TYPE_FROM_CLASS (object_class),                  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL /* closure */, NULL /* accumulator */, NULL /* accumulator data */, NULL /* C marshaller */,                  G_TYPE_NONE /* return_type */, 0 /* n_params */, NULL /* param_types */); 带信号机制的文件写方法: void viewer_file_write (ViewerFile   *self, const guint8 *buffer,                    gsize         size) {   g_return_if_fail (VIEWER_IS_FILE (self));   g_return_if_fail (buffer != NULL || size == 0); /* First write data. */ /* Then, notify user of data written. */ g_signal_emit (self, file_signals[CHANGED], 0 /* details */); } 用户回调处理函数连接到信号: g_signal_connect (file, "changed", (GCallback) changed_event, NULL); 4. 跨语言互通性 GObject被设计为可以直接使用在C程序中,并且可以封装至其他语言,如C++、Java、Ruby、Python和.NET/Mono等,这使得GObject具有很好的跨语言互通性。

    05-08 75浏览
  • 深入理解C语言中的编码:ASCII与二进制

    深入理解C语言中的编码:ASCII与二进制

    04-15 111浏览
  • 动态数码显示设计:灵动数字的视觉盛宴

    1. 如图4.13.1所示,P0端口接动态数码管的字形码笔段,P2端口接动态数码管的数位选择端,P1.7接一个开关,当开关接高电平时,显示“12345”字样;当开关接低电平时,显示“HELLO”字样。 2. 电路原理图   图4.13.1 3. 系...

    02-27 165浏览
  • 多路开关状态指示设计:掌控电路的智能之眼

    1. 如图4.3.1所示,AT89S51单片机的P1.0-P1.3接四个发光二极管L1-L4,P1.4-P1.7接了四个开关K1-K4,编程将开关的状态反映到发光二极管上。(开关闭合,对应的灯亮,开关断开,对应的灯灭)。 2. 电路原理图   图4.3.1...

    02-27 136浏览
  • 单片机C语言:程序在内存中的分布

    单片机C语言:程序在内存中的分布情况

    02-18 128浏览
  • 嵌入式软件为何要用状态机架构

    一、提高CPU使用效率 话说我只要见到满篇都是delay_ms()的程序就会头疼,动辄十几个ms几十个ms的软件延时是对CPU资源的巨大浪费,宝贵的CPU时间都浪费在了NOP指令上。 那种为了等待一个管脚电平跳变或者一个串口数据,让整个程序都不动的情况也让我非常纠结,如果事件一直不发生电平跳变,你要等到世界末日么? 如果应用状态机编程思想,程序只需要用全局变量记录下工作状态,就可以转头去干别的工作了,当然忙完那些活儿之后要再看看工作状态有没有变化。 只要目标事件(定时未到、电平没跳变、串口数据没收完)还没发生,工作状态就不会改变,程序就一直重复着“查询—干别的—查询—干别的”这样的循环,这样CPU就闲不下来了。 这种处理方法的实质就是在程序等待事件的过程中间隔性地插入一些有意义的工作,好让CPU不是一直无谓地等待。 二、逻辑完备性 逻辑完备性是状态机编程最大的优点。 不知道大家有没有用C语言写过计算器的小程序,我很早以前写过,写出来一测试,那个惨不忍睹啊! 当我规规矩矩的输入算式的时候,程序可以得到正确的计算结果,但要是故意输入数字和运算符号的随意组合,程序总是得出莫名其妙的结果。 后来我试着思维模拟一下程序的工作过程,正确的算式思路清晰,流程顺畅,可要碰上了不规矩的式子,走着走着我就晕菜了,那么多的标志位,那么多的变量,变来变去,最后直接分析不下去了。 很久之后我认识了状态机,才恍然明白,当时的程序是有逻辑漏洞的。 如果把这个计算器程序当做是一个反应式系统,那么一个数字或者运算符就可以看做一个事件,一个算式就是一组事件组合。 对于一个逻辑完备的反应式系统,不管什么样的事件组合,系统都能正确处理事件,而且系统自身的工作状态也一直处在可知可控的状态中。 反过来,如果一个系统的逻辑功能不完备,在某些特定事件组合的驱动下,系统就会进入一个不可知不可控的状态,与设计者的意图相悖。 状态机就能解决逻辑完备性的问题。 状态机是一种以系统状态为中心,以事件为变量的设计方法,它专注于各个状态的特点以及状态之间相互转换的关系。 状态的转换恰恰是事件引起的,那么在研究某个具体状态的时候,我们自然而然地会考虑任何一个事件对这个状态有什么样的影响。 这样,每一个状态中发生的每一个事件都会在我们的考虑之中,也就不会留下逻辑漏洞。 这样说也许大家会觉得太空洞,实践出真知,某天如果你真的要设计一个逻辑复杂的程序,会觉得状态机真香! 三、程序结构清晰 用状态机写出来的程序的结构是非常清晰的。 程序员最痛苦的事儿莫过于读别人写的代码,如果代码不是很规范,而且手里还没有流程图,读代码会让人晕了又晕,只有顺着程序一遍又一遍的看,很多遍之后才能隐约地明白程序大体的工作过程。 有流程图会好一点,但是如果程序比较大,流程图也不会画得多详细,很多细节上的过程还是要从代码中理解。 相比之下,用状态机写的程序要好很多,拿一张标准的UML状态转换图,再配上一些简明的文字说明,程序中的各个要素一览无余。 程序中有哪些状态,会发生哪些事件,状态机如何响应,响应之后跳转到哪个状态,这些都十分明朗,甚至许多动作细节都能从状态转换图中找到。 可以毫不夸张的说,有了UML状态转换图,程序流程图写都不用写。

    01-14 138浏览
  • 我彻底服了,大牛讲解信号与系统(通俗易懂)

    我彻底服了,大牛讲解信号与系统(通俗易懂) (2015-10-13 21:22:36) 转载▼ 分类: 电力电子技术 第一课什么是卷积卷积有什么用什么是傅利叶变换什么是拉普拉斯变换 引子 很多朋友和我一样,工科电子类专业,学了一堆...

    01-08 146浏览
  • 为什么建议你用表驱动法?嵌入式C语言代码开发技巧

    数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike 说明 本文基于这样的认识:数据是易变的,逻辑是稳定的。本文例举的编程实现多为代码片段,但不影响描述的完整性。本文例举的编程虽然基于C语言,但其编程思想也适用于其他语言。此外,本文不涉及语言相关的运行效率讨论。 概念提出 所谓表驱动法(Table-Driven Approach)简而言之就是用查表的方法获取数据。此处的“表”通常为数组,但可视为数据库的一种体现。 根据字典中的部首检字表查找读音未知的汉字就是典型的表驱动法,即以每个字的字形为依据,计算出一个索引值,并映射到对应的页数。相比一页一页地顺序翻字典查字,部首检字法效率极高。 具体到编程方面,在数据不多时可用逻辑判断语句(if…else或switch…case)来获取值;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就开始显现。 例如,用36进制(A表示10,B表示11,…)表示更大的数字,逻辑判断语句如下: 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'; } 当然也可以用 switch…case 结构,但实现都很冗长。而用表驱动法(将numChar 存入数组)则非常直观和简洁。如: CHAR aNumChars[] = {'0', '1', '2', /*3~9*/'A', 'B', 'C', /*D~Y*/'Z'}; CHAR ucNumChar = aNumChars[ucNum % sizeof(aNumChars)]; 像这样直接将变量当作下数组下标来读取数值的方法就是直接查表法。 注意,如果熟悉字符串操作,则上述写法可以更简洁: CHAR ucNumChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[ucNum]; 使用表驱动法时需要关注两个问题:一是如何查表,从表中读取正确的数据;二是表里存放什么,如数值或函数指针。前者参见1.1节“查表方式”内容,后者参见1.2节“实战示例”内容。 查表方式 常用的查表方式有直接查找、索引查找和分段查找等。 直接查找 即直接通过数组下标获取到数据。如果熟悉哈希表的话,可以很容易看出这种查表方式就是哈希表的直接访问法。 如获取星期名称,逻辑判断语句如下: if(0 == ucDay) {     pszDayName = "Sunday"; } else if(1 == ucDay) {     pszDayName = "Monday"; } //... ... else if(6 == ucDay) {     pszDayName = "Saturday"; } 而实现同样的功能,可将这些数据存储到一个表里: CHAR *paNumChars[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; CHAR *pszDayName = paNumChars[ucDay]; 类似哈希表特性,表驱动法适用于无需有序遍历数据,且数据量大小可提前预测的情况。 对于过于复杂和庞大的判断,可将数据存为文件,需要时加载文件初始化数组,从而在不修改程序的情况下调整里面的数值。 有时,访问之前需要先进行一次键值转换。如表驱动法表示端口忙闲时,需将槽位端口号映射为全局编号。所生成的端口数目大小的数组,其下标对应全局端口编号,元素值表示相应端口的忙闲状态。 索引查找 有时通过一次键值转换,依然无法把数据(如英文单词等)转为键值。此时可将转换的对应关系写到一个索引表里,即索引访问。 如现有100件商品,4位编号,范围从0000到9999。此时只需要申请一个长度为100的数组,且对应2位键值。但将4位的编号转换为2位的键值,可能过于复杂或没有规律,最合适的方法是建立一个保存该转换关系的索引表。采用索引访问既节省内存,又方便维护。比如索引A表示通过名称访问,索引B表示通过编号访问。 分段查找 通过确定数据所处的范围确定分类(下标)。有的数据可分成若干区间,即具有阶梯性,如分数等级。此时可将每个区间的上限(或下限)存到一个表中,将对应的值存到另一表中,通过第一个表确定所处的区段,再由区段下标在第二个表里读取相应数值。注意要留意端点,可用二分法查找,另外可考虑通过索引方法来代替。 如根据分数查绩效等级: #define MAX_GRADE_LEVEL (INT8U)5 DOUBLE 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]; } 上述两张表(数组)也可合并为一张表(结构体数组),如下所示: 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; } 该表结构已具备的数据库的雏形,并可扩展支持更为复杂的数据。其查表方式通常为索引查找,偶尔也为分段查找;当索引具有规律性(如连续整数)时,退化为直接查找。 使用分段查找法时应注意边界,将每一分段范围的上界值都考虑在内。 找出所有不在最高一级范围内的值,然后把剩下的值全部归入最高一级中。有时需要人为地为最高一级范围添加一个上界。 同时应小心不要错误地用“<”来代替“<=”。要保证循环在找出属于最高一级范围内的值后恰当地结束,同时也要保证恰当处理范围边界。 实战示例 本节多数示例取自实际项目。表形式为一维数组、二维数组和结构体数组;表内容有数据、字符串和函数指针。基于表驱动的思想,表形式和表内容可衍生出丰富的组合。 字符统计 问题:统计用户输入的一串数字中每个数字出现的次数。 普通解法主体代码如下: INT32U aDigitCharNum[10] = {0}; /* 输入字符串中各数字字符出现的次数 */ 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;     } } 这种解法的缺点显而易见,既不美观也不灵活。其问题关键在于未将数字字符与数组aDigitCharNum下标直接关联起来。 以下示出更简洁的实现方式: for(; dwStrIdx < dwStrLen; dwStrIdx++) { aDigitCharNum[szDigits[dwStrIdx] - '0']++; } 上述实现考虑到0也为数字字符。该解法也可扩展至统计所有ASCII可见字符。 月天校验 问题:对给定年份和月份的天数进行校验(需区分平年和闰年)。 普通解法主体代码如下: 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; } 以下示出更简洁的实现方式: #define MONTH_OF_YEAR 12 /* 一年中的月份数 */ /* 闰年:能被4整除且不能被100整除,或能被400整除 */ #define IS_LEAP_YEAR(year) ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) /* 平年中的各月天数,下标对应月份 */ 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; } 名称构造 问题:根据WAN接口承载的业务类型(Bitmap)构造业务类型名称字符串。 普通解法主体代码如下: 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");     } } 以下示出C语言中更简洁的实现方式: /* 获取var变量第bit位,编号从右至左 */ #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]);     } } 新的实现将数据和逻辑分离,维护起来非常方便。只要逻辑(规则)不变,则唯一可能的改动就是数据(paSvrNames)。 值名解析 问题:根据枚举变量取值输出其对应的字符串,如PORT_FE(1)输出“Fe”。 //值名映射表结构体定义,用于数值解析器 typedef struct{     INT32U dwElem; //待解析数值,通常为枚举变量 CHAR*  pszName; //指向数值所对应解析名字符串的指针 }T_NAME_PARSER; /****************************************************************************** * 函数名称:  NameParser * 功能说明:  数值解析器,将给定数值转换为对应的具名字符串 * 输入参数:  VOID *pvMap       :值名映射表数组,含T_NAME_PARSER结构体类型元素                                 VOID指针允许用户在保持成员数目和类型不变的前提下,                                 定制更有意义的结构体名和/或成员名。              INT32U dwEntryNum :值名映射表数组条目数              INT32U dwElem     :待解析数值,通常为枚举变量              INT8U* pszDefName :缺省具名字符串指针,可为空 * 输出参数:  NA * 返回值  :  INT8U *: 数值所对应的具名字符串              当无法解析给定数值时,若pszDefName为空,则返回数值对应的16进制格式              字符串;否则返回pszDefName。 ******************************************************************************/ INT8U *NameParser(VOID *pvMap, INT32U dwEntryNum, INT32U dwElem, INT8U* pszDefName) {     CHECK_SINGLE_POINTER(pvMap, "NullPoniter");     INT32U dwEntryIdx = 0; for(dwEntryIdx = 0; dwEntryIdx < dwEntryNum; dwEntryIdx++) { T_NAME_PARSER *ptNameParser = (T_NAME_PARSER *)pvMap; if(dwElem == ptNameParser->dwElem)         { return ptNameParser->pszName;         } //ANSI标准禁止对void指针进行算法操作;GNU标准则指定void*算法操作与char*一致。 //若考虑移植性,可将pvMap类型改为INT8U*,或定义INT8U*局部变量指向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;     } } 以下给出NameParser的简单应用示例: //UNI端口类型值名映射表结构体定义 typedef struct{     INT32U dwPortType;     INT8U* pszPortName; }T_PORT_NAME; //UNI端口类型解析器 T_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] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("Unknown", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0, "Unknown")) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("DefName", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0, "DefName")) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("Fe", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 1, "Unknown")) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("Pots", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 3, "Unknown")) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("Vuni", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 99, NULL)) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("Unknown", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 255, "Unknown")) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("0xABCD", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0xABCD, NULL)) ? "ERROR" : "OK");     printf("[%s] Result: %s!\n", __FUNCTION__, ucTestIndex++,            strcmp("NullPoniter", NameParser(NULL, UNI_NAM_MAP_NUM, 0xABCD, NULL)) ? "ERROR" : "OK"); } gUniNameMap在实际项目中有十余个条目,若采用逻辑链实现将非常冗长。 取值映射 问题:不同模块间同一参数枚举值取值可能有所差异,需要适配。 此处不再给出普通的switch…case或if…else if…else结构,而直接示出以下表驱动实现: 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; } 相应地,从loopMIBState映射到loopMEState需要定义一个ConvertLoopMIBStateToMEState函数。更进一步,所有类似的一对一映射关系都必须如上的映射(转换)函数,相当繁琐。事实上,从抽象层面看,该映射关系非常简单。提取共性后定义带参数宏,如下所示: /********************************************************** * 功能描述:进行二维数组映射表的一对一映射,用于参数适配 * 参数说明:map        -- 二维数组映射表             elemSrc    -- 映射源,即待映射的元素值             elemDest   -- 映射源对应的映射结果             direction  -- 映射方向字节,表示从数组哪列映射至哪列。                           高4位对应映射源列,低4位对应映射结果列。             defaultVal -- 映射失败时置映射结果为缺省值 * 示例:ARRAY_MAPPER(gCesLoopMap, 3, ucLoop, 0x10, NO_LOOP);             则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) 参数取值转换时直接调用统一的映射器宏,如下: 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); 另举一例: #define CES_DEFAULT_JITTERBUF (INT32U)2000 /* 默认jitterbuf为2000us,而1帧=125us */ #define CES_JITTERBUF_STEP (INT32U)125 /* jitterbuf步长为125us,即1帧 */ #define CES_DEFAULT_QUEUESIZE (INT32U)5 #define CES_DEFAULT_MAX_QUEUESIZE (INT32U)7 #define ARRAY_NUM(array) (sizeof(array) / sizeof((array)[0])) /* 数组元素个数 */ typedef struct{     INT32U  dwJitterBuffer;     INT32U  dwFramePerPkt;     INT32U  dwQueueSize; }QUEUE_SIZE_MAP; /* gCesQueueSizeMap也可以(JitterBuffer / FramePerPkt)值为索引,更加紧凑 */ 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}}; /********************************************************** * 函数名称:CalcQueueSize * 功能描述:根据JitterBuffer和FramePerPkt计算QueueSize * 注意事项:配置的最大缓存深度 *            = 2 * JitterBuffer / FramePerPkt *            = 2 * N Packet = 2 ^ QueueSize *            JitterBuffer为125us帧速率的倍数, *            FramePerPkt为每个分组的帧数, *            QueueSize向上取整,最大为7。 **********************************************************/ INT32U CalcQueueSize(INT32U dwJitterBuffer, INT32U dwFramePerPkt) {     INT8U ucIdx = 0, ucNum = 0; //本函数暂时仅考虑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; } 版本控制 问题:控制OLT与ONU之间的版本协商。ONU本地设置三比特控制字,其中bit2(MSB)~bit0(LSB)分别对应0x21、0x30和0xAA版本号;且bitX为0表示上报对应版本号,bitX为1表示不上报对应版本号。其他版本号如0x20、0x13和0x1必须上报,即不受控制。 最初的实现采用if…else if…else结构,代码非常冗长,如下: 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; } //此处省略gOamCtrlCode == 2~6的处理代码 else 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; } 以下示出C语言中更简洁的实现方式(基于二维数组): /********************************************************************** * 版本控制字数组定义 * gOamCtrlCode:   Bitmap控制字。Bit-X为0时上报对应版本,Bit-X为1时屏蔽对应版本。 * CTRL_VERS_NUM:  可控版本个数。 * CTRL_CODE_NUM:  控制字个数。与CTRL_VERS_NUM有关。 * gOamVerCtrlMap: 版本控制字数组。行对应控制字,列对应可控版本。                   元素值为0时不上报对应版本,元素值非0时上报该元素值。 * Note: 该数组旨在实现“数据与控制隔离”。后续若要新增可控版本,只需修改                   -- CTRL_VERS_NUM                   -- gOamVerCtrlMap新增行(控制字)                   -- gOamVerCtrlMap新增列(可控版本) **********************************************************************/ #define CTRL_VERS_NUM 3 #define CTRL_CODE_NUM (1< u8_t gOamVerCtrlMap[CTRL_CODE_NUM][CTRL_VERS_NUM]

    01-03 156浏览
  • C语言函数的返回值的潜规则

    基本上,没有人会将大段的C语言代码全部塞入 main() 函数。更好的做法是按照复用率高、耦合性低的原则,尽可能的将代码拆分不同的功能模块,并封装成函数。C语言代码的组合千变万化,因此函数的功能可能会比较复杂,不同的输入,常常产生不同的输出结果。 C语言函数的返回值 C语言函数可以通过返回值表示输出结果,例如 log() 函数的返回值会根据不同的输入,返回不同的值。再比如,我们定义一个函数 myopen(),用于打开某个文件,那么,这个函数要么能够成功打开文件,要么打开文件失败,这时,可以通过返回值区分“成功”和“失败”。当然,myopen() 函数失败的原因可能很多,但不是本文关注的重点,本文更关心的是,该以何值表示“成功”,何值表示“失败”。按照C语言语法, 0 表示假,非零(常常用 1)表示真,那是否函数也用 0 返回值表示“失败”,1 返回值表示“成功”呢? 行业“潜规则” C语言函数当然可以使用返回值 0 表示“失败”,用返回值 1 表示“成功”。事实上,C语言函数用什么样的返回值表示成功或者失败,只是一种人为的约定,函数的调用者遵守这个“约定”就可以了。C语言也有“行业潜规则”,不过,对于一般的函数,大多数C语言程序员常常使用返回值 0 表示成功,非零值表示失败。因此,如果希望我们定义的函数能够被大多数人愉快的使用,最好不要反其道而行,遵守“行业潜规则”更好一点。仔细考虑下,其实C语言函数使用返回值 0 表示成功是有原因的。更一般的C语言函数返回值并不一定只有两种可能值(成功/失败),它可能还会返回对应错误原因的返回值。总之,函数成功只有一种可能,函数失败却有多种可能。实数要么是 0,要么非 0,因此可以将 0 看作一个比较特殊的“唯一”数值,使用 0 这个“唯一”的返回值,表示唯一的“成功”,多种非零的返回值,表示多种原因的失败,无疑更好一些。当然,我们也可以说“实数要么是 1,要么非 1”,不过这显然也不是“行业潜规则”。例如我们可以规定,如果 myopen() 函数因为“文件或者目录不存在”的原因失败,返回 -1,如果因为“权限不够”的原因失败,则返回 -2。-1 和 -2 都是“非零值”,而成功作为失败的对立面,也即“非零值”的对立面,myopen() 函数使用返回值 0 表示成功无可厚非。 另一种行业“潜规则” 当然,C语言程序员中还有一种“行业潜规则”。如果定义的函数是个布尔函数,也即返回值显式的使用类似于 bool 关键字定义,或者函数名类似于 is_true(),那么显然此时应该遵守C语言语法,使用“真”值表示成功,“假”值表示失败。 if( is_true() ) printf("true\n"); 请看上面这两行C语言代码,显然,遵守C语言语法的布尔函数更便于程序员写出布尔判断类的代码。 小结 本文主要讨论了C语言程序开发中关于函数返回值的问题。可见,使用什么样的返回值表示成功,什么用的返回值表示失败,其实只是一种人为约定。只不过,如果希望我们编写的代码能够被大多数同行愉快的使用,最好遵守下“行业潜规则”。当然了,若是希望我们的C语言代码应用性更广,则可以使用标准头文件里预先定义好的 EXIT_SUCCESS 和 EXIT_FAILURE 宏。

    01-02 146浏览
  • C语言中的“悬空指针”和“野指针”是什么意思?

    提起C语言大部分开发者很自然就会想到指针二字,没错,作为C的核心和灵魂,它的地位咱们就不再赘述了,今天我们想

    2024-12-19 114浏览
正在努力加载更多...
广告