小言_互联网的博客

C语言复习——嵌入式相关

375人阅读  评论(0)

文章目录

一、作用域与存储类

1.1作用域

1.1.1代码块作用域(局部变量)

代码块:包含在开始花括号和对应结束花括号之内的一段代码。
作用域:从定义变量位置到该代码块的末尾。

1.1.2文件作用域(全局变量)

在所有函数之外定义的变量具有文件作用域。
作用域:从定义变量位置到包含该定义的文件的结尾处。在其它位置使用该变量应用extern来声明该变量。

1.2存储时期

1.2.1静态存储时期

程序运行期间一直存在,给变量分配固定的存储空间;
所有全局变量具有静态存储时期(具有静态存储时期的并不都是全局变量),但是修饰全局变量的关键词static表明的是链接类型,并非存储时期。

1.2.2动态(自动)存储时期

运行期间根据需要动态的给变量分配存储空间。

5种存储类

二、内存与指针

2.1内存分配方式

2.1.1从静态存储区分配

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量;

2.1.2从栈上创建

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限;

2.1.3动态内存分配(malloc)

程序在运行的时候由程序员自己负责申请和释放内存。

2.2内存陷阱

C语言的灵活性,指针的应用,以及C语法的宽容性很容易造成代码的错误,这其中最主要的就是内存单元的溢出。

  • 堆栈溢出(本来已经规定只能进10个元素,超过10个之后就会溢出)
  • 数组越界

将错误锁定在一个函数中以后,首先应该关注内存的问题。

举例,看看下面这段代码有什么问题

char *DoSomething(
(
char i[32*1024];//1024=1k,即32k
return i;//返回数组的首地址
}

2.2.1两个重大问题

  • 临时变量在堆栈上创建,太大的临时变量数组会冲掉堆栈;
  • 返回堆栈中的地址是没有意义的因为堆栈中的内容永远是不确定的,因为堆栈会在函数执行完之后,立刻就会释放掉,是局部变量。

2.2.2 数组越界的危险

  • 临时数组:在栈上创建,因此临时数组越界有可能导致某些局部变量莫明其妙被改,函数返回时崩溃等等;
  • 全局数组:在静态存储区创建,因此全局数据越界有可能导致某些全局变量莫明其妙被改,被冲的动态内存无法释放等等。

2.3指针是什么?

指针是一个变量,它的值是另外一个变量的地址。

2.3.1指针的类型

指针所指向的变量的类型,就是指针的类型。
举例,右边的运算有何不同:

int X[2],*pX=&X[O];pX++;
char Y[2],*pY=&Y[O];pY++;

2.3.2指针的三要素

  • 指针指向的地址;
  • 指针指向地址上的内容;
  • 指针本身的地址;

举例:

int A,*pA,**ppA;
pA = &A;
ppA = &pA;

2.3.3指针的大小(指针变量占用的内存空间)

举例,以下为ARM平台下的一段32位C程序,请计算sizeof的值。

char str[] ="Hello";
char *p = str;
int n = 10;
sizeof(str) = 6
sizeof(p) = 4
sizeof(n) = 4

2.3.4指针的初始化

指针变量在没有被初始化之前,它的值是随机的;一个指向不明的指针是非常危险的。
当创建一个指针时,系统只分配了用来存储指针本身的内存空间,并不分配用来存储数据的内存空间。使用指针之前,必须给它赋予一个已分配的内存地址。

2.3.5指针与数组

举例,下列操作是否合法:

int a[4],*p;
p = a;//等价于p=&a[0];
*(a+2)= 0x00;//等价于a[2];
p[2] = 0x01;等价于*(p+2);

但是数组名不同于指针:
数组名a是指向数组起始位置的“常量”因此不能对数组名进行赋值操作。

a = p;//错误
a++;//错误

指针与数组什么时候是相同的

举例,以下为ARM平台下的一段32位C程序,请计算sizeof的值。

void Func(char a[100])
{
   
sizeof(a) = 4
}
  • 数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;
  • 在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

三、位操作

3.1位操作的用途

  • 硬件寄存器控制;
  • 压缩和加密操作;
  • 提高程序运行效率;

因为位是程序可以操作的最小数据单位,所以理论上可以用“位操作”完成所有的运算与操作。

3.2位运算符位逻辑运算符

&位与(两个数对应位都为1结果才为1)
|位或(两个数对应位有一个为1结果则为1 )
^位异或(两个数对应位不同结果则为1 )
~按位取反(单目运算符)
移位运算符
<<左移 、>>右移

3.3用法:掩码

位操作符通常跟掩码一起用。掩码是某些位为开而某些位为关的位组合。
例:

flag &=MASK;

3.4用法:打开位、关闭位、转置位打开位

flag = flag | MASK;或flag |= MASK;关闭位:
flag = flag & ~MASK;或flag &= ~MASK;转置位:
flag = flag  MASK;或flag ^=MASK;

3.5用法:查看某一位的值错误用法

if(flag == MASK)正确用法:
if((flag&MASK) == MASK)

位运算符的优先级低于==,因此需要在flag&MASK的两侧加上圆括号。

3.6用法:移位操作

unsigned char ch=OxO7; //00000111
unsigned char i;
i = ch <<6;
i = ch > > 2;

四、预编译处理

C编译系统在对程序进行编译之前,先进行预处理。
C提供的预处理功能主要有以下三种:

  • 宏定义
  • 文件包含
  • 条件编译

4.1宏定义类对象宏

#define STX Ox02类函数宏
#define SQUARE(x)((x)*(x))几点说明:
  • 宏定义不是C语句,不在行末加分号;
  • 宏名有效范围为从定义处到本源文件结束;
  • c.在宏定义时,可以引用已经定义的宏名;
  • d.宏替换不占运行时间,只占编译时间;

4.2文件包含:#include

预处理器发现#include后,就会寻找后跟的这个文件并把这个文件的内容复制到当前位置替换#include指令;
使用#include指令的一些例子:

4.3条件编译

条件编译指不对整个程序编译,而是编译满足条件的那部分。条件编译有以下几种形式:

#ifdef标识符
	程序段1;
#else
	程序段2;
#endif

#ifdef的作用:当标识符在前面已经定义过,则对程序段1进行编译,否则对程序段2进行编译。

#ifndef标识符
	程序段1;
#else
	程序段2;
#endif

#ifndef的作用和#ifdef相反,当标识符之前没被定义过,则对程序段1进行编译,否则就对程序段2进行编译。

#if表达式
	程序段1;
#else
	程序段2;
#endif

#if的作用:当表达式的值为真时,对程序段1进行编译,都则就对程序段2进行编译;
举例,下面是某工程中.h文件中的一段程序,请说明
#ifndef/#define/#endif结构的作用。

#ifndef DEF_H
#define DEF_H   //防止头文件被重复引用
#include <math.h>
#include "graphics.h"
#endif

五、编程规范

5.1什么是好的程序

  • 易读:理想的情况:能把一个程序代码读出声音来,别人听了后就知道这个程序是解决什么问题的,是如何解决的
  • 精练简洁
  • 运行快
  • 结构明了

5.2程序设计风格

5.2.1标识符的命名

推荐—种C程序标识符命名法

5.2.1.1标识符命名应注意的一些细节书写格式编程习惯——变量命名加前缀

匈牙利命名法为C程序标识符的命名定义了一种非常标准化的方式,这种命名方式是以两条规则为基础的:

  • 变量的名字以一个或者多个小写字母前缀开头,前缀能够体现变量数据类型、作用域等信息。
  • 在标识符内,前缀以后就是一个或者多个第一个字母大写的单词,这些单词清楚地指出了该标识符的作用。

变量命名加前缀
c——char
s——short
n——int nDoorNum
l——long
b——boolean取值只为真和假的整型变量如bValid
f——float浮点数
d——double数组a[5]

5.2.1.2推荐一种C程序标识符命名法——变量名中单词开头字母大写,其他字母小写
  • 常用的意义明显的变量,如i,j,k,坐标x,y等不必遵循
  • StudentName,TeacherName

常量和宏都是大写,单词之间用_分隔

#define MAX_WIDTH5
#define PI 3.14
#define ABS(x)((x)>=0?(x) :-(x))
5.2.1.3标识符命名应注意的一些细节
  • 标识符号应能提供足够信息,最好是可以发音的。

  • 为全局变量取长的,描述信息多的名字,为局部变量取短名字

  • 名字太长时可以适当采用单词的缩写。但要注意,缩写方式要一致。要缩写就全都缩写。
    比如单词Number,如果在某个变量里缩写成了:int nDoorNum;那么最好包含Number单词的变量都缩写成Num.

  • 注意使用单词的复数形式。如int nTotalStudents, nStudents;容易让人理解成代表学生数目,而nStudent含义就不十分明显

  • 对于返回值为真或假的函数,加“ls"前缀如:
    int lsCanceled(); int isalpha();
    C语言标准库函数BOOLlsButtonPushed();

  • 对于获取某个数值的函数,加“Get"前缀
    char * GetFileName();

  • 对于设置某个数值的函数,加"Set前缀
    void SetMaxVolume();

  • 一般变量和结构名用名词,函数名用动词或动宾词组

5.3程序书写格式注意事项

5.3.1正确使用缩进

首先,一定要有缩进,否则代码的层次不明显。缩进应为4个空格较好。需要缩进时一律按Tab键,或一律按空格键,不要有时用Tab键缩进,有时用空格键缩进。一般开发环境都能设置一个Tab键相当于多少个空格,此时就都用Tab键

5.3.2行宽与折行

一行不要太长,不能超过显示区域。以免阅读不便。太长则应折行。折行最好发生在运算符前面,不要发生在运算符后面如

//不要这样
if( Condition1() && Condition2()
&& Condition3() ){
   
}
//要这样
if( Condition1() && Condition2()&& Condition3() )
{
   
}

5.3.3注意{}位置不可随意,要统—

如果写了:

if ( condition1() ) {
   
DoSomething();
}

别处就不要写(我比较喜欢这样,觉得这样结构会更加清晰的!)

if( condition2())
{
   
DoSomething() ;
}

5.3.4变量和运算符之间最好加1个空格

int nAge = 5;
nAge = 4;if( nAge >= 4)
printf("%d",nAge);
for( i = 0; i < 100; i++ );

5.4一些好的编程习惯

5.4.1尽量不要用立即数,而用#define(C++中用const)定义成常量,以便以后修改

#define MAX_STUDENTS 20
struct sStudent aStudents [MAX_STUDENTS];
struct SStudent aStudents [20];
#define TOTAL_ELEMENTS 100
for( i = 0; i<TOTAL_ELEMENTS; i++) 
{
   
}

5.4.2带参数的宏定义应该在整个宏体外加括号,而且宏参数都要用括号包裹

#define SQUARE(x) x*x //平方SQUARE(k+1);变成k+1*k+1;//错
//即使是
#define SQUARE(x)(x)*(x)//也不保险
1/SQUARE(x);变成1/(x)*(x);//错
//应该是这样
#define SQUARE(x)((x)*(x))//平方

5.4.3稍复杂的表达式中要积极使用括号,以免优先级理解上的混乱

n = k +++ j;//不好
n = ( k++) + j;//好─点

5.4.4不很容易理解的表达式应分几行写

n = ( k++) + j;
//应该写成:
n = k + j;
k++;

5.4.5不提倡在表达式中使用?∶形式,而用if … else语句替代

xp =2 * k < ( n-m) ? c[k+1] : d[k--];
if(2*k <(n-m))
	xp = c[k+1];
else
	xp = d[k--];

5.4.6嵌套的if else 语句要多使用{}

if( Condition1())
	if( condition2)
		DoSomething();
	else
		NoCondition2();

不够好,应该:

if( Condition1() ) 
{
   
	if( condition2())
		DoSomething();
	else
		NoCondition2();
}

5.4.7应避免if else的多重嵌套,而用并列的完成。

if( Condition1() ) 
{
   
	if ( Condition2() ) 
	{
   
		if( Condition3() )
		{
   
			condition123();
		}
		else 
		{
   
			NoCondition3();
		}
	}
	else 
	{
   
		NoCondition2();
	}
}
else 
{
   
	NoCondiction1();
}

替换为:

if( ! condition1 ) 
{
   
	NoCondition1();
}
else if(! condition2 )
{
   
	NoCondition2();
}
else if(! condition3) 
{
   
	NoCondition3();
}
else 
{
   
	Condition123();
}

5.4.8遵循一些惯例的写法,如:循环的固定写法:

for( i = 0;i<n ;i++ )
array[i] = 0;//而非i = 0 ;
while( i <= n-1)
array[i++]=0;//死循环写法:
//for( ; ; ){ .…}或while(1){ ...}

5.4.9写出来的代码应该容易读出声比如

if( !( n > m ) && !(s > t))//就不如
if( ( m <= n ) && ( t <=s ))
if( !( c == 'y' || c =='z'))//不如
if( c |= 'y' && c |= 'z');

六、开发高效程序的技巧

6.1ARM编程中局部变量的使用举例

请看一面一段程序:

int checksum(int *data)
{
   
	char i;
	for(i=0;i<64;i++)
	{
   
	}
}
//汇编程序
ADD	r1,r1,#1
AND	r1,r1,#0xFF
CMP	r1,#0x40

把上面的程序段将i声明为unsigned int类型,
则汇编程序为

ADD r1,r1,#1
CMP r1,#0x40

ARM编程中局部变量类型的使用:
char或short类型并不比int类型占用更小的寄存器空间或者堆栈空间,应尽量不要使用char或short作为局部变量,以防止做不必要的转换;除非要使用char或short的溢出归零特性,如255+1=0。

6.2ARM编程中函数参数的传递

4寄存器原则(four-register rule)
ARM-Thumb过程调用标准(ATPCS)定义了{RO-R3}四个寄存器作为参数传递和结果返回寄存器,如果参数超过4个,则使用堆栈进行传递(额外的指令和慢速的存储器操作)。因为内部寄存器的访问速度要远远大于存储器,所以我们应尽量把函数的参数控制在4个以下。

6.3结构体安排

举例,对下面两个结构体进行比较

struct A 
{
   
	char a;
	int b;
	char c;
	short d;
}
struct B 
{
   
	char a;
	char c;
	short d;
	int b;
}
  • 结构体A的内存空间分配:


a只要一个字节,b要连续四个字节存放,所以a前面的三个字节是空的,这是编译器认为的最优化的结构

  • 结构体B的内存空间分配:
    (刚刚好)

6.4 C语言中的封装思想举例,给出一个人机界面

要求:以中间的按键切换液晶显示菜单,左右两个按键对当前项的值进行增减操作。
初级程序

void onLeftKey()
{
   
	switch(currentFocus)
	{
   
		case MENU1:menu1onLeft();break;
		case MENU2:menu2onLeft();break;
	}
}
void onMidKey()
{
   
	currentFocus++;
	switch(currentFocus)
	{
   
		case MENU1:MenuText = "....";break;
	}
}
void onRightKey()
{
   
	switch(currentFocus)
	{
   
		case MENU1:menu1onRight();break;
		case MENU2:menu2onRight();break;
	}
}
//将菜单的属性和操作“封装”在—起
typedef struct KeyAdjMenu
{
   
	UCHAR*text;//液晶显示文本
	void (*onAdjKey)(int key);
	void (*onMidKey)();
)
//定义菜单时,只需这样:
struct KeyAdjMenu menu[NUM]=
{
   
	{
    "menu1" , menu1onAdj,onMidKey},
	{
    "menu2" , menu2onAdj,onMidKey},
	...
}

//按健的处理变成:
switch(key)
{
   
	case LEFT:
	case RIGHT:menu[currentFocus].onAdjKey(key);break;
	case MIDDLE:currentFocuS++;
				if(currentFocus > NUM)
					currentFocus = o;
					LcdDisplay(menu[currentFocus].text);
				break;
)

6.5 Bug的修正

  • 别急着改,想想,再想想,想清楚了再动手;
  • 考虑所做的修改是否对系统造成新的影响;
  • 考虑是否对全局数据结构或其他人的代码造成影响;
  • 修改完了,应该有详细的代码注释和文档,并对修改过的代码进行测试。

6.6 一些有益的建议

  • 长期坚持好的Coding Style;
  • 避免编写技巧性很高的代码;
  • 长期坚持良好的文档写作习惯;
  • 不要崇拜那些独来独往、不受约束且带点邪气的所谓“真正的编程高手”;
  • 基础最重要,坚持学习,天天向上。

转载:https://blog.csdn.net/weixin_45870610/article/details/117293379
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场