嵌入式系统高级C语言编程

来源:互联网 发布:嵌入式linux移植 编辑:程序博客网 时间:2024/05/23 01:17
一个整型数
int a;
一个指向整型数的指针
int *a;
一个指向指针的指针,它指向的指针是指向一个整型数
int **a;
一个有10个整型数的数组
int a[10];
一个有10个指针的数组,该指针是指向一个整型数的
int*a[10];
一个指向函数的指针,该函数有一个整型参数并返回一个整型数
int (*a)(int);
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参并返回一个整型数
int (*a[10])(int);

关键字static的作用是什么?
在C语言中,关键字static有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所有函数访问,但不能被模块外
其它函数访问,它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其他函数调用。那就是,这个函数被限制在声明它的
模块的本地范围内使用。


const int a;
int const a;
const int *a;
int * const a;
int const *a const;
前两个的作用是一样,a 是一个常整型数;
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可以修改的,但指针可以)
第四个意思a是一个指向整型数的常指针(指针指向的整型数是可以修改的,但是指针是不可以修改的)
最后一个意味着a是一个指向常整型数的常指针(指针指向的整型数是不可修改的,同时指针也是不可修改的)

const的优点:关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个
参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。
通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
合理地使用关键字const可以使编译器很自然的保护那些不希望被改变的参数,防止其被无意的代码修改。
简而言之,可以减少bug的实现。

一个定义为volatile的变量是说这个变量可能会被意想不到地改变。这样,编译器就不会去假设这个变量的值了。
精确的说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器的备份。下面是
volatile变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量
3)多线程应用中被几个任务共享的变量。

嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a
的bit 3。在以上两个操作中,要保持其它位不变。
$define BIT3(0x1<<3)
static int a;
void set_bit3(void)
{
a|=BIT3;
}

void clear_bit3(void)
{
a &=~BIT3;
}

嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量
的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
int *ptr;
ptr=(int * )0x67a9;
*ptr=0xaa66;

 

 

 

中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展--让标准C支持中断。其代表事实是,产生了
了一个新的_interrupt.下面的代码就使用了_interrupt关键字去定义了一个中断服务子程序(ISR),请评价下这段代码.
_interrupt double compute_area(double radius)
{
 double area=PI*radius*radius;
 printf("Area=%f",area);
 return area;
}
这个函数有太多的错误了
1)ISR不能返回一个值。
2)ISR不能传递参数。
3)在许多的处理器/编译器,浮点一般都是不可重入的。有些处理器/编译器需要让额外的寄存器入栈,有些
处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4)与第三点一脉相承,printf()经常有重入和性能上的问题。

 


下面的代码输出是什么,为什么?
void foo(void)
{
 unsigned int a=6;
 int b=-20;
 (a+b>6)?puts(">6"):puts("<=6");
}
这无符号整型问题的答案是输出是">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作都自动转换为无符号
类型。因为-20变成了一个非常大的正整数,所有该表达式计算出的结果大于6。这一点对于频繁用到无符号数据类型的嵌入式系统是
非常重要的。


C语言同意一些令人震惊的结构,下面的结构是合法的吗。如果是,它做些什么?
int a=5,b=7,c;
c=a+++b;

//a=6;b=7;c=12;
这是个关于代码的可读性,代码的可修改性的好的话题。

 

字符指针变量与字符数组的区别

字符数组由若干元素组成,每个元素存放一个字符,而字符指针变量只存放字符串的首地址,不是整个字符串。

char *str[14];

str="i love c!";此时赋给str的不是字符,而是字符串首地址。

static char *name[]={"li jing","wang ji","hongjiujing"};不要以为数组中存放的是字符串,它存放的是字符串首地址。

 void指针

ANSI新增了一种void *指针类型,即定义一个指针变量,但不指向任何数据类型,等用到的时候再强制转换类型。

如:

char *p1;

void *p2;

p1=(char *)p2;

也可以将一个函数定义成void *型:

如:void *func(ch1,ch2)

表示函数fun返回一个地址,它指向空类型,如果需要用到此地址,可以使用强制转换,如(假设p1为char型);

p1=(char *)fun(c1,c2);

 所谓位运算就是指进行二进制位的运算。在系统软件中,常要处理二进制位的问题。C提供的位运算符有:

&按位与 |按位或  ^按位异或者说  ~取反  <<左移  >>右移

&对于将一个单元清零,取一个数中的某些指定位以及保留指定位有很大用途。

|常被用来将一个数的某些位置1。^判断两个位值,不同为1,相同为0。常用来使特定位翻转等。

~常用来配置配合其它位运算符使用的,常用来设置屏蔽字。<<将一个数的各二进制位全部位移,高位左移后溢出,舍弃不起作用。左移一位相当于该数乘2,左移n位相当于乘2n。左移比乘法运算要快的多。

>>右称移时,要注意符号问题,对无符号数,右移时左边高位移位0,对于有符号数,如果原来符号位为0(正数);则左边移入0;如果符号位为1(负数),则左边移入0还是1要取决于系统。移入0称为逻辑右移,移入1称为算数右移。

位段

将一个字符分为几段存放几个信息。所谓位段是以位为单位定义长度的结构体类型中的成员。如:

struct packed-data{

unsigned a:2;

unsigned b:6;

unsigned c:4;

unsigned d:4;

int l;

}data;

其中a,b,c,d分别占2位,6位,4位,4位。l为整数,占4个字节。对于位段成员的引用如下:

data.a=2;等,但要注意赋值时,不要超出位段定义的范围。如位段成员a定义为2位,最大值为3,即(11)2,所以data.a=5;就会取5的两个低位进行赋值,就得不到想要的值了。

关于位段的定义和引用,有几点重要说明:

1、若某一个段要从另一个字开始存放,可以定义:

unsigned a:1;

unsigned b:2;

unsigned :0;

unsigned c:3;(另一单位)

使用长度为0的位段,作用就是使下一个位段从下一个存储单元开始存放。

2一个位段必须存放在用一个存储单元中,不能跨两个单元。

3可以定义无名位段。如:

unsigned a:1;

unsigned :2;(这两位空间不用)

unsigned b:3;

4、位段的长度不能大于存储单元的长度,也不能定义位段数组。

 指针的初始化
变量在没有赋值之前,其值不定的。对于指针变量,可以表述为:指向不明。
程序访问了一个没
有初始化的指针:
int* p;
p 的内存是随机的
一个数,比如:
0x3FF0073D
程序随即访问内存
地址:
0x3FF0073D
0x3FF0073D 是哪里的内存?说不定正好是
Windows老大要用的内存,你竟敢访问!
Windows一生气,蓝屏。
一个指向不明的指
针,是非常危险的!!!
因此,指针在使用前一定要初始化;在使用前一定要确定指针是非空的!!!

 空指针与通用指针
(1). 空指针
是个特殊指针值,也是唯一对任何指针类型都合法的指针值。一个指针变量具有
空指针值,表示它当时没指向有意义的东西,处于闲置状态。空指针值用0 表
示,这个值绝不会是任何程序对象的地址。给一个指针赋值0 就表示要它不指向
任何有意义的东西。为了提高程序的可读性,标准库定义了一个与0 等价的符号
常量NULL,程序里可以写:
p = NULL; //注意不要与空字符NUL混淆,NUL等价于‘/0’
或者:
p = 0;
注意:
在编程时,应该将处于闲置的指针赋为空指针;
在调用指针前一定要判断是否为空指针,只有在非空情况下才能调用。

 

 

函数指针即指向函数地址的指针。利用该指针可以知道函数在内在中的位置。因为也可以利用函数指针调用函数。

 int (*fun)(void)这是,func就是一个函数指针

注意:int *func(void)和int (*func)(void)的区别

int *func(void)//这是返回一个整型指针的函数   int(*func)(void) //这是一个函数指针

不能将普通变量的地址赋给函数指针,不能将函数的调用赋给函数指针  可以将函数名赋给一个函数指针

函数指针的用途:一旦函数可以通过指针被传递、被记录,这开启了许多应用,特别是下列三者:

1 多态(polymorphism):指用一个名字定义不同的函数,这函数执行不同但又类似的操作,从而实现“一个接口,多种方法”。

2、多线程:将函数指针传进负责建立多线程的API中:例如win32的CreateThread(...pF...)。

3。回调:所谓的回调机制就是:当发生某事件时,自动呼叫某段程序代码。事件驱动的系统经常透过函数指针来实现回调机制,例如Win32的WinProc其实就是一种回调,用来处理窗口听讯息。

函数指针数组:

double add(double,double);

double sub(double,double);

double (*open_func[ ](double,double)={add,sub,...};

第2个步骤是用下面语句替换前面整条switch语句:

result=oper_func[oper](op1,op2);

oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。

ASIX Window中的函数指针

typedef struct window_class
{
U8 wndclass_id;
STATUS (*create)(char *caption, U32 style, U16 x, U16 y, U16 width,
U16 hight, U32 wndid, U32 menu, void **ctrl_str, void *exdata);
STATUS (*destroy)(void *ctrl_str);
STATUS (*msg_proc)( U32 win_id, U16 asix_msg, U32 lparam, void
*data, U16 wparam, void *reserved);
STATUS (*msg_trans)(void *ctrl_str, U16 msg_type, U32 areaId,
P_U16 data, U32 size, PMSG trans_msg);
STATUS (*repaint)(void *ctrl_str, U32 lparam);
STATUS (*move)(void *ctrl_str, U16 x, U16 y, U16 width, U16 hight,
void *reserved);
STATUS (*enable)(void *ctrl_str, U8 enable);
STATUS (*caption)(void *ctrl_str, char *caption, void *exdata);
STATUS (*information)(void *ctrl_str, struct asix_window *wndinfo);
} WNDCLASS;

 

WNDCLASS WindowClass[] = {
{WNDCLASS_WIN, wn_create, wn_destroy, wn_msgproc,wn_msgtrans, wn_repaint, NULL,NULL,wn_caption, NULL},
{WNDCLASS_BUTTON,Btn_create,Btn_destroy,Btn_msg_proc,Btn_msg_trans,Btn_repaint,NULL,Btn_enable,Btn_caption,
NULL},
{WNDCLASS_SELECT,sl_create, sl_destroy, sl_msg_proc, sl_msg_trans, sl_repaint,NULL, sl_enable, sl_caption, NULL},
{WNDCLASS_SELECTCARD,NULL, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL},
{WNDCLASS_MENU,menu_create, menu_destroy, menu_msgproc, menu_msgtrans, mn_repaint, NULL, NULL,NULL,NULL},
{WNDCLASS_LIST, Lbox_create, Lbox_destroy, Lbox_msgproc, Lbox_msgtrans, lb_repaint, NULL, NULL,NULL,NULL},
{WNDCLASS_KEYBD, kbd_create,kbd_destroy, kbd_msgproc, kbd_msgtrans, kbd_repaint, NULL, NULL,NULL,NULL},
{WNDCLASS_SCROLL,sb_create, sb_destroy, sb_msgproc, sb_msgtrans, sb_repaint, NULL,sb_enable, NULL,NULL},
{WNDCLASS_KEYBAR,kb_create, kb_destroy, kb_msgproc, kb_msgtrans,NULL,NULL, NULL,NULL, NULL},
#ifdef ASIX_DEBUG
{WNDCLASS_TEST,tst_create, tst_destroy, tst_msgproc, tst_msgtrans, NULL,NULL,NULL,NULL,NULL}
#endif
};

 

1 结构体
结构是由若干(可不同类型的)数据项组合而成的复合数据对象,这些数据
项称为结构的成分或成员。
(1) 字段
C 语言的结构还提供了一种定义字段的机制,使人在需要时能把几个结构成
员压缩到一个基本数据类型成员里存放,这可以看作是一种数据压缩表示方
式。
例16: struct pack {
unsigned a:2;
unsigned b:8;
unsigned c:6;
} pk1, pk2;
结构变量pk1或者pk2的三个成员将总共占用16位存储,其中a占用2 位,b占
用8 位,c占用6 位。

(2)结构体内部的成员的对齐
在计算结构体长度(尤其是用sizeof)时,需要注意!
根据不同的编译器和处理器,结构体内部的成员有不同的对齐方式,这
会引起结构体长度的不确定性。
例17:
#include <stdio.h>
struct a{ char a1; char a2; char a3; }A;
struct b{ short a2; char a1; }B;
void main(void)
{
printf(“%d,%d,%d,%d”, sizeof(char), sizeof(short), sizeof(A), sizeof(B));
}
在Turbo C 2.0中结果都是
1,2,3,3
在VC6.0中是
1,2,3,4

www.cnasic.com
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构首地址的偏移量(offset)都是成员大小的整数
倍,
如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会
在最末一个成员之后加上填充字节(trailing padding)。
对于上面的准则,有几点需要说明:
1)结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这
个宏也在stddef.h中定义,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类
型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复
合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合
类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员
的偏移位置时则是将复合类型作为整体看待。

2 联合体
在一个结构(变量)里,结构的各成员顺序排列存储,每个成员都有自
己独立的存储位置。联合变量的所有成员共享从同一片存储区。因此一个联
合变量在每个时刻里只能保存它的某一个成员的值。
(1)联合变量的初始化
联合变量也在可以定义时直接进行初始化,但这个初始化只能对第一个成员
做。例如下面的描述定义了一个联合变量,并进行了初始化:
例18:
union data
{
char n;
float f;
};
union data u1 = {3}; //只有u1.n被初始化

 

3 枚举
枚举是一种用于定义一组命名常量的机制,以这种方式定义的常量一般称为
枚举常量。
一个枚举说明不但引进了一组常量名,同时也为每个常量确定了一个整数值。
缺省情况下其第一个常量自动给值0,随后的常量值顺序递增。
(1)给枚举常量指定特定值
与给变量指定初始值的形式类似。如果给某个枚举量指定了值,跟随其后的
没有指定值的枚举常量也将跟着顺序递增取值,直到下一个有指定值的常量为止。
例如写出下面枚举说明:
enum color {RED = 1, GREEN, BLUE, WHITE = 11, GREY, BLACK= 15};
这时,RED、GREEN,、BLUE 的值将分别是1、2、3,WHITE、GREY的值
将分别是11、12,而BLACK 的值是15。

 

(2)用枚举常量作为数组长度
例19:
typedef enum{WHITE, RED, BLUE, YELLOW, BLACK, COLOR_NUM
}COLOR;
… …
float BallSize[COLOR_NUM];
上例中当颜色数量发生变化时,只需在枚举类型定义中加入或删去颜色。
无需修改COLOR_NUM的定义。与大量使用#define相比既简洁又可靠。如:
typedef enum{ WHITE, RED, BLUE,COLOR_NUM }COLOR;

4 sizeof的定义和使用
sizeof 是C/C++中的一个操作符(注意!不是函数!就像return一样)。
其作用就是返回一个对象或者类型所占的内存字节数。
sizeof有三种使用形式,如下:
1) sizeof(var); // sizeof( 变量);
2) sizeof(type_name); // sizeof( 类型);
3) sizeof var; // sizeof 变量;
所以,
int i;
sizeof(i); // ok
sizeof i; // ok
sizeof(int); // ok
sizeof int; // error
为求形式统一,不建议采用第3种写法,忘掉它吧!

数组的sizeof
数组的sizeof值等于数组所占用的内存字节数,如:
例21:char* ss = "0123456789";
sizeof(ss); // 结果4 , ss是指向字符串常量的字符指针
sizeof(*ss); // 结果1 ,*ss是第一个字符
char ss[] = "0123456789";
sizeof(ss); // 结果11 , 计算到‘/0’位置,因此是10+1
sizeof(*ss); // 结果1 , *ss是第一个字符
char ss[100] = "0123456789";
sizeof(ss); // 结果100 , 表示在内存中的大小100×1
strlen(ss); // 结果10 , strlen是到‘/0’为止之前的长度
int ss[100] = "0123456789";
sizeof(ss); //结果200 , ss表示在内存中的大小100×2
strlen(ss); //错误,strlen的参数只能是char*且必须以‘/0’结尾

 
原创粉丝点击