吴建英单片机开发板地址
店铺:
【吴建英店】
地址:
【】
仅仅因为程序可以运行并不一定意味着你的代码是好的C代码。 衡量代码的质量,应该从以下几个方面来看:
1.代码稳定,无隐患。
2、执行效率高。
3、可读性高。
4、易于移植。
以下是我在网上看到的一些技巧以及我自己的一些经验,与大家分享;
1、如果可能的话,尽量少用库函数,以方便不同MCU和编译器之间的移植。
2.选择合适的算法和数据结构
你应该熟悉算法语言,知道各种算法的优缺点。 具体信息请参考相应的参考资料,很多计算机书籍上都有介绍。 用较快的二分查找或乱序查找方法代替较慢的顺序查找方法,用快速排序、归并排序或根排序代替插入排序或冒泡排序方法,可以大大提高程序执行的效率。 选择合适的数据结构也很重要。 例如,如果在一堆随机存储的数字中使用大量的插入和删除指令,那么使用链表会快得多。 数组和指针语句具有非常加密的关系。 一般来说,指针更加灵活、简洁,而数组更加直观、易于理解。 对于大多数编译器来说,使用指针比使用数组生成更短的代码和更高效的执行。 但在 Keil 中情况恰恰相反,使用数组生成的代码比使用指针更短。
3.尽可能使用最小的数据类型
可以使用字符类型(char)定义的变量不应使用整型(int)变量定义; 可以使用整型变量定义的变量不应使用长整型(long int)定义,也不应使用浮点型(float)。 ) 变量,不要使用浮点变量。 当然,定义变量后,不要超出变量的范围。 如果给变量赋值超出了范围,C编译器不会报错,但程序运行结果会出错,而且这种错误很难发现。 在ICCAVR中,可以在Options中设置printf参数的使用。 尝试使用基本参数(%c、%d、%x、%X、%u 和 %s 格式说明符)并使用较短的长整数参数(%ld、%lu、%lx 和 %lX 格式说明符)。 至于浮点参数(%f),尽量不要使用。 其他 C 编译器也是如此。 在其他条件不变的情况下,使用%f参数会大大增加生成代码量并降低执行速度。
4.使用自增和自减指令
通常,使用自增、自减指令和复合赋值表达式(如a-=1和a+=1等)可以生成高质量的程序代码。 编译器通常可以生成 inc 和 dec 等指令,而使用 a=a+1 或 a=a-1 等指令,很多 C 编译器会生成两到三个字节的指令。 上述写法生成的代码在ICCAVR、GCCAVR、IAR等适合AVR单片机的C编译器中是一样的,也能生成高质量的inc、dec代码。
5、降低计算强度
您可以将复杂的表达式替换为需要较少计算但具有相同功能的表达式。 如下:
(1)、余数运算。
a=a%8;
可以改为:
a=a&7;
注意:位运算只需要一个指令周期即可完成,而C编译器的“%”运算大多是通过调用子程序来完成,导致代码长、执行速度慢。 通常,如果唯一的要求是求 2n 平方的余数,则可以使用位运算来代替。
(2)、平方运算
a=pow(a,2.0);
可以改为:
a=a*a;
注意:在内置硬件乘法器的单片机(如51系列)中,乘法运算比平方运算要快得多,因为浮点数的平方是通过调用子程序来实现的。 在内置硬件乘法器的AVR中,在ATMega163等微控制器中,乘法运算只需2个时钟周期即可完成。 即使在没有内置硬件乘法器的AVR微控制器中,乘法运算的子程序也比平方运算的子程序具有更短的代码和更快的执行速度。
如果是求三次方,比如:
a=pow(a,3.0);
改成:
a=a*a*a;
效率的提升更加明显。
(3)利用移位实现乘除运算
a=a*4;
b=b/4;
可以改为:
a=a>2;
注意:通常如果需要乘或除2n,可以使用移位方法代替。 在ICCAVR中,如果乘以2n,则可以生成左移代码,而当乘以其他整数或除以任何数字时,则调用乘法和除法子例程。 通过移位方法生成的代码比通过调用乘法和除法子例程生成的代码更高效。 其实只要乘以或除以一个整数,通过移位就可以得到结果,如:
a=a*9
可以改为:
a=(a0;i–)
两个函数的延迟效果相似,但几乎所有C编译器为后一个函数生成的代码都比前一个代码少1到3个字节,因为几乎所有MCU都有0传输的指令。 可以使用后一种方法来生成这样的指令。 使用 while 循环时也是如此。 使用自减指令控制循环将比使用自增指令控制循环生成少1到3个字母的代码。 但当循环中有通过循环变量“i”读写数组的指令时,使用预减循环时,可能会出现数组越界的情况,请注意。
(3)while循环和do…while循环
使用while循环时有两种循环形式:
无符号整型 i;
我=0;
当我
我++;
//用户程序
或者:
无符号整型 i;
我=1000;
做
我 – ;
//用户程序
而(i>0);
两种循环中,do…while 循环编译后生成的代码长度比 while 循环短。
7.查表
程序中一般不会进行非常复杂的运算,例如浮点数的乘法、除法、平方根,以及一些复杂数学模型的插值运算等。 对于这些消耗时间和资源的操作,应尽可能使用查表方法。 ,并将数据表放入程序存储区。 如果很难直接生成所需的表,可以尝试提前启动,以减少程序执行时重复计算的工作量。
8. 其他
例如,使用在线汇编,将字符串和一些常量保存在程序内存中,有利于优化。
C语言宏定义技巧(常用宏定义)
要想写好C语言,漂亮的宏定义非常重要。 使用宏定义可以防止错误,提高可移植性、可读性、方便性等。下面列出了成熟软件中常用的一些宏定义。 。 。 。 。 。
相关链接:《初学者入门系列》优秀C语言编程风格讲解:
代码:
1.防止头文件被重复包含
#ifndef COMDEF_H
#定义COMDEF_H
//头文件内容
#万一
2、重新定义了部分类型,防止因各个平台和编译器的差异而导致类型字节的差异,更容易移植。
typedef 无符号字符布尔值; /* 布尔值类型。 */
typedef 无符号长整型 uint32; /* 无符号 32 位值 */
typedef 无符号短整型 uint16; /* 无符号 16 位值 */
typedef 无符号字符 uint8; /* 无符号8位值*/
typedef 有符号长整型 int32; /* 有符号的 32 位值 */
typedef 有符号短整型16; /* 有符号的 16 位值 */
typedef 有符号 char int8; /* 有符号的 8 位值 */
//以下不推荐使用
typedef 无符号字符字节; /* 无符号 8 位值类型。 */
typedef 无符号短字; /* 无符号 16 位值类型。 */
typedef 无符号长双字; /* 无符号 32 位值类型。 */
typedef 无符号字符 uint1; /* 无符号 8 位值类型。 */
typedef 无符号短整型 uint2; /* 无符号 16 位值类型。 */
typedef 无符号长 uint4; /* 无符号 32 位值类型。 */
typedef 有符号的 char int1; /* 有符号 8 位值类型。 */
typedef 有符号短整型2; /* 有符号 16 位值类型。 */
typedef 长整型 int4; /* 有符号 32 位值类型。 */
typedef 有符号长型 sint31; /* 有符号的 32 位值 */
typedef 有符号短整型 sint15; /* 有符号的 16 位值 */
typedef 有符号的 char sint7; /* 有符号的 8 位值 */
3. 获取指定地址处的一个字节或字
#define MEM_B( x ) ( *( (字节 *) (x) ) )
#define MEM_W( x ) ( *( (字 *) (x) ) )
4. 求最大值和最小值
#define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )
#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )
5.获取结构体(struct)中某个字段的偏移量
#define FPOS(类型, 字段)
/*lint -e545 */ ( (dword) &(( type *) 0)-> 字段 ) /*lint +e545 */
6.获取结构体中字段占用的字节数
#define FSIZ( 类型, 字段 ) sizeof( ((类型 *) 0)->字段 )
7、将两个字节按照LSB格式转换成一个Word
#define FLIPW( 射线 ) ( (((word) (射线)[0]) * 256) + (射线)[1] )
8、按照LSB格式将一个Word转换为两个字节
#define FLOPW( 射线, val )
(射线)[0] = ((val)/256);
(射线)[1] = ((val) & 0xFF)
9.获取变量的地址(字宽)
#define B_PTR( var ) ( (字节 *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void *) &(var) )
10.获取一个字的高低字节
#define WORD_LO(xxx) ((字节) ((字)(xxx) & 255))
#define WORD_HI(xxx) ((字节) ((字)(xxx) >> 8))
11,返回大于 X 的最接近的 8 倍数
#define RND8( x ) ((((x) + 7) / 8 ) * 8 )
12. 将字母转换为大写
#define UPCASE( c ) ( ((c) >= 'a' && (c) = '0' && (c) = '0' && (c) = 'A' && (c) = 'a' && ( c) (val)) ? (val)+1 : (val))
16.返回数组元素的个数
#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
17. 返回无符号数n尾值 MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#define MOD_BY_POWER_OF_TWO( val, mod_by )
( (dword)(val) & (dword)((mod_by)-1) )
18、对于存储空间中IO空间映射的结构,输入输出处理
#define inp(端口) (*((易失性字节 *) (端口)))
#define inpw(端口) (*((易失性字 *) (端口)))
#define inpdw(端口) (*((易失性双字 *)(端口)))
#define outp(端口, val) (*((易失性字节 *) (端口)) = ((字节) (val)))
#define outpw(端口, val) (*((易失性字 *) (端口)) = ((字) (val)))
#define outpdw(端口, val) (*((易失性双字 *) (端口)) = ((双字) (val)))
19.使用一些宏来跟踪和调试
ANSI 标准指定了五个预定义的宏名称。 他们是:
_ 线 _
_文件_
_ 日期 _
_时间_
_STDC_
如果编译不标准,可能只支持上面的几个宏名称,或者根本不支持。 请记住,编译器还可能提供其他预定义的宏名称。
_ LINE _ 和 _ FILE _ 宏在 # 行部分讨论,其余的宏名称在这里讨论。
_DATE_ 宏包含月/日/年形式的字符串,表示源文件转换为代码的日期。
源代码翻译成目标代码的时间以字符串形式包含在 TIME 中。 字符串格式为小时:分钟:秒。
如果实现是标准的,则宏 _ STDC _ 包含十进制常量 1。如果它包含任何其他数字,则实现是非标准的。
可以定义宏,例如:定义_DEBUG时,输出数据信息和文件的行。
#ifdef_DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
#别的
#define DEBUGMSG(消息,日期)
#万一
20. 宏定义应包含在括号内,以防止使用时出错。
例如:#define ADD(a,b) (a+b)
使用 do{}while(0) 语句包含多个语句以防止错误
例如:#difne DO(a,b) a+b;
一个++;
应用时: if(….)
DO(a,b); //发生错误
别的
解:#difne DO(a,b) do{a+b;
a++;}同时(0)
宏中“#”和“##”的用法
1. 一般用法
我们使用#将宏参数转换为字符串,并使用##将两个宏参数连接在一起。
用法:
#包括
#包括
使用命名空间 std;
#定义 STR #s
#define CONS(a,b) int(a##e##b)
int main()
printf(STR(vck)); // 输出字符串“vck”
printf("%dn", CONS(2,3)); // 2e3 输出:2000
返回0;
2. 当宏参数是另一个宏时
需要注意的是,宏定义中凡是使用‘#’或‘##’,宏参数都不会被扩展。
1.非'#'和'##'的情况
#定义 TOW (2)
#定义 MUL(a,b) (a*b)
printf("%d*%d=%dn", TOW, TOW, MUL(TOW,TOW));
该行的宏将扩展为:
printf("%d*%d=%dn", (2), (2), ((2)*(2)));
MUL中的参数TOW将展开为(2)。
2、当有'#'或'##'时
#定义A (2)
#定义 STR #s
#define CONS(a,b) int(a##e##b)
printf("int 最大值: %sn", STR(INT_MAX)); // INT_MAX #include
该行将扩展为:
printf("int 最大值: %sn", "INT_MAX");
printf("%sn", CONS(A, A)); // 编译错误
这行是:
printf("%sn", int(AeA));
INT_MAX和A都不会再次展开,但是解决这个问题的方法很简单。 再添加一层中间转换宏。 添加这一层宏的目的是扩展这一层的所有宏参数,那么在转换宏里面的哪个宏(_STR)就可以得到正确的宏参数。
#定义A (2)
#定义_STR(s)#s
#define STR(s) _STR(s) // 转换宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) // 转换宏
printf("int 最大值: %sn", STR(INT_MAX)); // INT_MAX,int类型的最大值,是一个变量#include
输出为: int max: 0x7ffffffff
STR(INT_MAX) –> _STR(0x7fffffff) 然后转换为字符串;
printf("%dn", CONS(A, A));
输出为:200
CONS(A, A) –> _CONS((2), (2)) –> int((2)e(2))
3、‘#’和‘##’的一些特殊应用案例
1.合并匿名变量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(类型,行) ___ANONYMOUS1(类型,_anonymous,行)
#define ANONYMOUS(类型) __ANONYMOUS0(类型, __LINE__)
示例:ANONYMOUS(static int); 即:static int _anonymous70; 70代表行号;
第一层:ANONYMOUS(static int); –> __ANONYMOUS0(静态 int, __LINE__);
第二层: –> ___ANONYMOUS1(static int, _anonymous, 70);
第三层: –> static int _anonymous70;
即每次只能解锁当前层的宏,所以__LINE__只能在第二层解锁;
2. 填充结构
#define FILL(a) {a, #a}
枚举 IDD{OPEN, CLOSE};
typedef 结构体 MSG{
国际长途电话号码;
常量字符 * 消息;
}味精;
MSG _msg[] = {填充(打开), 填充(关闭)};
相当于:
MSG _msg[] = {{打开, "打开"},
{关闭,“关闭”}};
3.记录文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4、获取数值类型对应的字符串缓冲区大小 #define _TYPE_BUF_SIZE(type) sizeof #type #define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type) char buf[TYPE_BUF_SIZE(INT_MAX)]; –> char buf[_TYPE_BUF_SIZE(0x7ffffffff )]; –> char buf[sizeof "0x7fffffff"]; 这相当于: char buf[11];
喜欢这篇文章的朋友请点赞哦
技术源于积累,成功源于坚持
——吴建英详细讲授单片机