嵌入式C精华.pdf

[复制链接]
2155|29
手机看帖
扫描二维码
随时随地手机跟帖
给力芯片|  楼主 | 2015-5-30 21:15 | 显示全部楼层 |阅读模式
嵌入式C精华

嵌入式C精华.pdf

1.68 MB

给力芯片|  楼主 | 2015-5-30 21:15 | 显示全部楼层
1.    struct 的巨大作用   
   面对一个人的大型 C/C++程序时,只看其对 struct 的使用情况我们就可以对其编写者的编程经
验进行评估。因为一个大型的 C/C++程序,势必要涉及一些(甚至大量)进行数据组合的结构体,这些结
构体可以将原本意义属于一个整体的数据组合在一起。从某种程度上来说,会不会用 struct,怎样用
struct 是区别一个开发人员是否具备丰富开发经历的标志。  
   在网络协议、通信控制、嵌入式系统的 C/C++编程中,我们经常要传送的不是简单的字节流(char
型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体。  
   经验不足的开发人员往往将所有需要传送的内容依顺序保存在 char 型数组中,通过指针偏移的
方法传送网络报文等信息。这样做编程复杂,易出错,而且一旦控制方式及通信协议有所变化,程序
就要进行非常细致的修改。  
   一个有经验的开发者则灵活运用结构体,举一个例子,假设网络或控制协议中需要传送三种报
文,其格式分别为 packetA、packetB、packetC:  
struct structA  
{  
int a;  
char b;  
};  
  
struct structB  
{  
char a;  
short b;  
};  
  
struct structC  
{  
int a;  
char b;  
float c;  
}

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:16 | 显示全部楼层
优秀的程序设计者这样设计传送的报文:  
struct CommuPacket  
{  
3
int iPacketType;    //报文类型标志  
union       //每次传送的是三种报文中的一种,使用 union  
{  
   struct structA packetA; struct structB packetB;  
   struct structC packetC;  
}  
};  
在进行报文传送时,直接传送 struct CommuPacket 一个整体。  
假设发送函数的原形如下:  
// pSendData:发送字节流的首地址,iLen:要发送的长度   
Send(char * pSendData, unsigned int  iLen);  
发送方可以直接进行如下调用发送 struct CommuPacket 的一个实例 sendCommuPacket:  
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );  
假设接收函数的原形如下:  
// pRecvData:发送字节流的首地址,iLen:要接收的长度  
//返回值:实际接收到的字节数  
unsigned int Recv(char * pRecvData, unsigned int  iLen);  
接收方可以直接进行如下调用将接收到的数据保存在 struct CommuPacket 的一个实例 recvC ommuPacket 中:  
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );  
接着判断报文类型进行相应处理:  
switch(recvCommuPacket. iPacketType)  
{  
case PACKET_A:  
…    //A 类报文处理  
break;  
case PACKET_B:  
…    //B 类报文处理  
break;  
case PACKET_C:  
…   //C 类报文处理  
break;  
}  
以上程序中最值得注意的是  
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );  
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );  
中的强制类型转换:(char *)&sendCommuPacket 、(char *)&recvCommuPacket,先取地址,再转化为 char 型指针,
这样就可以直接利用处理字节流的函数。  
利用这种强制类型转化,我们还可以方便程序的编写,例如要对 sendCommuPacket 所处内存初始化为 0,可以这
样调用标准库函数 memset():  
memset((char *)&sendCommuPacket,0, sizeof(CommuPacket));  

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:16 | 显示全部楼层
2. struct的成员对齐  
Intel、微软等公司曾经出过一道类似的面试题:  
#include <iostream.h>  
4
#pragma pack(8)  
struct example1  
{
short a;  
long b;  
};  
struct example2  
{  
  char c;  
  example1 struct1;  
  short e;     
};  
#pragma pack()  
int main(int argc, char* argv[])  
{  
example2 struct2;  
cout << sizeof(example1) << endl;  
cout << sizeof(example2) << endl;  
cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;  
return 0;  
}  
问程序的输入结果是什么?  
答案是:  
8  
16  
4  
不明白?还是不明白?下面一一道来

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:16 | 显示全部楼层
2. struct的成员对齐  
Intel、微软等公司曾经出过一道类似的面试题:  
#include <iostream.h>  
4
#pragma pack(8)  
struct example1  
{
short a;  
long b;  
};  
struct example2  
{  
  char c;  
  example1 struct1;  
  short e;     
};  
#pragma pack()  
int main(int argc, char* argv[])  
{  
example2 struct2;  
cout << sizeof(example1) << endl;  
cout << sizeof(example2) << endl;  
cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;  
return 0;  
}  
问程序的输入结果是什么?  
答案是:  
8  
16  
4  
不明白?还是不明白?下面一一道来:  
2.1 自然对界  
struct 是一种复合数据类型,其构成元素既可以是基本数据类型(如 int、long、float 等)的变量,也可以是
一些复合数据类型(如 array、struct、union 等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,
以提高运算效率。缺省情况下,编译器为结构体的每个成员按其自然对界(natural alignment)条件分配空间。各
个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。  
自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中 size 最大的成员对齐。  
例如:  
struct naturalalign  
{  
  char a;  
  short b;  
  char c;  
};  
在上述结构体中,size 最大的是 short,其长度为 2 字节,因而结构体中的 char 成员 a、c 都以 2 为单位对齐,
sizeof(naturalalign)的结果等于 6;  
如果改为:  
struct naturalalign  
5
{  
  char a;  
  int b;   
  char c;  
};  
其结果显然为 12。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:17 | 显示全部楼层
2.2 指定对界  
一般地,可以通过下面的方法来改变缺省的对界条件:  
  · 使用伪指令#pragma pack (n),编译器将按照 n 个字节对齐;  
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。   
注意:如果#pragma pack (n)中指定的 n 大于结构体中最大成员的 size,则其不起作用,结构体
仍然按照 size 最大的成员进行对界。  
例如:  
#pragma pack (n)  
struct naturalalign  
{  
  char a;  
  int b;  
  char c;  
};  
#pragma pack ()  
当 n 为 4、8、16 时,其对齐方式均一样,sizeof(naturalalign)的结果都等于 12。而当n为2
时,其发挥了作用,使得 sizeof(naturalalign)的结果为 6。  
在 VC++ 6.0 编译器中,我们可以指定其对界方式(见图 1),其操作方式为依次选择 projetct >
setting > C/C++菜单,在 struct member alignment 中指定你要的对界方式。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:17 | 显示全部楼层
图片1
QQ截图20150530211749.jpg

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:18 | 显示全部楼层
另外,通过__attribute((aligned (n)))也可以让所作用的结构体成员对齐在 n 字节边界上,但
是它较少被使用,因而不作详细讲解。  
2.3  面试题的解答  
至此,我们可以对 Intel、微软的面试题进行全面的解答。  
程序中第 2 行#pragma pack (8)虽然指定了对界为 8,但是由于 struct example1 中的成员最大
size 为 4(long 变量 size 为 4),故 struct example1 仍然按 4 字节对界,struct example1 的 s ize
为 8,即第 18 行的输出结果;  
struct example2 中包含了 struct example1,其本身包含的简单数据成员的最大 size 为 2(short
变量 e),但是因为其包含了 struct example1,而 struct example1 中的最大成员 size 为 4,struct
example2 也应以 4 对界,#pragma pack (8)中指定的对界对 struct example2 也不起作用,故 19 行的
输出结果为 16;  
由于 struct example2 中的成员以 4 为单位对界,故其 char 变量 c 后应补充 3 个空,其后才是
成员 struct1 的内存空间,20 行的输出结果为 4。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:18 | 显示全部楼层
3.      C 和 C++间 struct 的深层区别  
在 C++语言中 struct 具有了“类”  的功能,其与关键字 class 的区别在于 struct 中成员变量
和函数的默认访问权限为 public,而 class 的为 private。  
例如,定义 struct 类和 class 类:  
struct structA  
{  
char a;  
…  
}  
class classB  
{  
      char a;  
      …  
}  
则:  
structA a;  
a.a = 'a';    //访问 public 成员,合法  
classB b;  
b.a = 'a';    //访问 private 成员,不合法  
许多文献写到这里就认为已经给出了 C++中 struct 和 class 的全部区别,实则不然,另外一点
需要注意的是:  
C++中的 struct 保持了对 C 中 struct 的全面兼容(这符合 C++的初衷——“a better c”),
因而,下面的操作是合法的:  
//定义 struct  
struct structA  
{  
char a;  
char b;  
int c;  
};  
7
structA a = {'a' , 'a' ,1};    //  定义时直接赋初值  
即 struct 可以在定义的时候直接以{ }对其成员变量赋初值,而 class 则不能,在经典书目
《thinking C++ 2
nd
edition》中作者对此点进行了强调。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:19 | 显示全部楼层
4.      struct 编程注意事项  
看看下面的程序:  
1. #include <iostream.h>  
2. struct structA  
3. {  
4.   int iMember;  
5.   char *cMember;  
6. };  
7. int main(int argc, char* argv[])  
8.{  
9.   structA instant1,instant2;  
10.  char c = 'a';      
11.   instant1.iMember = 1;  
12.   instant1.cMember = &c;     
13.  instant2 = instant1;     
14.  cout << *(instant1.cMember) << endl;     
15.  *(instant2.cMember) = 'b';   
16.   cout << *(instant1.cMember) << endl;     
17.   return 0;  
}  
14 行的输出结果是:a  
16 行的输出结果是:b  
Why?我们在 15 行对 instant2 的修改改变了 instant1 中成员的值!  
原因在于 13 行的 instant2 = instant1 赋值语句采用的是变量逐个拷贝,这使得 instant1 和
instant2 中的 cMember 指向了同一片内存,因而对 instant2 的修改也是对 instant1 的修改。  
在 C 语言中,当结构体中存在指针型成员时,一定要注意在采用赋值语句时是否将 2 个实例中的
指针型成员指向了同一片内存。  
在 C++语言中,当结构体中存在指针型成员时,我们需要重写 struct 的拷贝构造函数并进行“=”
操作符重载。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 21:19 | 显示全部楼层
C++中 extern "C"含义深层探索  
作者:宋宝华  e-mail: 21cnbao@21cn.com  出处:太平洋电脑网
1.引言  
    C++语言的创建初衷是“a better C”,但是这并不意味着 C++中类似 C 语言的全局变量和函数
所采用的编译和连接方式与 C 语言完全相同。作为一种欲与 C 兼容的语言,C++保留了一部分过程式语
言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。
8
但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与 C
有明显的不同。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:05 | 显示全部楼层
C++中 extern "C"含义深层探索  
作者:宋宝华  e-mail: 21cnbao@21cn.com  出处:太平洋电脑网
1.引言  
    C++语言的创建初衷是“a better C”,但是这并不意味着 C++中类似 C 语言的全局变量和函数
所采用的编译和连接方式与 C 语言完全相同。作为一种欲与 C 兼容的语言,C++保留了一部分过程式语
言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。
8
但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与 C
有明显的不同。  
2.从标准头文件说起  
某企业曾经给出如下的一道面试题:  
面试题  
为什么标准头文件都有类似以下的结构?  
   #ifndef __INCvxWorksh  
   #define __INCvxWorksh  
   #ifdef __cplusplus  
   extern "C" {  
   #endif  
   /*...*/  
   #ifdef __cplusplus  
   }  
   #endif  
   #endif /* __INCvxWorksh */  
分析  
显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用
是防止该头文件被重复引用。
那么  
   #ifdef __cplusplus  
   extern "C" {  
   #endif  
  #ifdef __cplusplus  
   }  
   #endif  
的作用又是什么呢?我们将在下文一一道来。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:10 | 显示全部楼层
3.深层揭密 extern "C"  
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,
被它修饰的目标是“C”的。让我们来详细解读这两重含义。  
(1)被 extern "C"限定的函数或变量是 extern 类型的;  
extern 是 C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,
其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:  
extern int a;  
仅仅是一个变量的声明,其并不是在定义变量 a,并未为 a 分配内存空间。变量 a 在所有模块中作
为一种全局变量只能被定义一次,否则会出现连接错误。  
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字 extern 声明。
例如,如果模块 B 欲引用该模块 A 中定义的全局变量和函数时只需包含模块 A 的头文件即可。这样,
模块 B 中调用模块 A 中的函数时,在编译阶段,模块 B 虽然找不到该函数,但是并不会报错;它会在
连接阶段中从模块 A 编译生成的目标代码中找到此函数。  
与 extern 对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个
函数或变量只可能被本模块使用时,其不可能被 extern “C”修饰。  
(2)被 extern "C"修饰的变量和函数是按照 C 语言方式编译和连接的;  
未加 extern “C”声明时的编译方式  
9
首先看看 C++中对类似 C 的函数是怎样编译的。
作为一种面向对象的语言,C++支持函数重载,而过程式语言 C 则不支持。函数被 C++编译后在符
号库中的名字与 C 语言的不同。例如,假设某个函数的原型为:  
void foo( int x, int y );  
该函数被 C 编译器编译后在符号库中的名字为_foo,而 C++编译器则会产生像_foo_int_int 之类
的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled
name”)。_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来
实现函数重载的。例如,在 C++中,函数 void foo( int x, int y )与 void foo( int x, float y )
编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成
员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,
也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加 extern "C"声明时的连接方式  
假设在 C++中,模块 A 的头文件如下:
// 模块 A 头文件  moduleA.h  
#ifndef MODULE_A_H  
   #define MODULE_A_H  
    int foo( int x, int y );  
#endif  
在模块 B 中引用该函数:
// 模块 B 实现文件  moduleB.cpp  
#include "moduleA.h"  
foo(2,3);  
实际上,在连接阶段,连接器会从模块 A 生成的目标文件 moduleA.obj 中寻找_foo_int_int 这样
的符号!
加 extern "C"声明后的编译和连接方式  
加 extern "C"声明后,模块 A 的头文件变为:  
// 模块 A 头文件  moduleA.h  
#ifndef MODULE_A_H  
   #define MODULE_A_H  
    extern "C" int foo( int x, int y );  
#endif  
在模块 B 的实现文件中仍然调用 foo( 2,3 ),其结果是:
(1)模块 A 编译生成 foo 的目标代码时,没有对其名字进行特殊处理,采用了 C 语言的方式;
(2)连接器在为模块 B 的目标代码寻找 foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块 A 中函数声明了 foo 为 extern "C"类型,而模块 B 中包含的是 extern int foo( int x,
int y ) ,则模块 B 找不到模块 A 中的函数;反之亦然。
所以,可以用一句话概括 extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生
都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么
做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):  
实现 C++与 C 及其它语言的混合编程。  
明白了 C++中 extern "C"的设立动机,我们下面来具体分析 extern "C"通常的使用技巧。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:11 | 显示全部楼层
4.extern "C"的惯用法  
10
(1)在 C++中引用 C 语言中的函数和变量,在包含 C 语言头文件(假设为 cExample.h)时,需进
行下列处理:
extern "C"  
{  
#include "cExample.h"  
}  
而在 C 语言的头文件中,对其外部函数只能指定为 extern 类型,C 语言中不支持 extern "C"声明,
在.c 文件中包含了 extern "C"时会出现编译语法错误。
笔者编写的 C++引用 C 函数例子工程中包含的三个文件的源代码如下:
/* c 语言头文件:cExample.h */  
#ifndef C_EXAMPLE_H  
#define C_EXAMPLE_H  
extern int add(int x,int y);  
#endif  
/* c 语言实现文件:cExample.c */  
#include "cExample.h"  
int add( int x, int y )  
{  
  return x + y;  
}  
// c++实现文件,调用 add:cppFile.cpp  
extern "C"  
{  
#include "cExample.h"  
}  
int main(int argc, char* argv[])  
{  
  add(2,3);   
  return 0;  
}  
如果 C++调用一个 C 语言编写的.DLL 时,当包括.DLL 的头文件或声明接口函数时,应加 extern "C"
{  }。
(2)在 C 中引用 C++语言中的函数和变量时,C++的头文件需添加 extern "C",但是在 C 语言中不
能直接引用声明了 extern "C"的该头文件,应该仅将 C 文件中将 C++中定义的 extern "C"函数声明为
extern 类型。
笔者编写的 C 引用 C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h  
#ifndef CPP_EXAMPLE_H  
#define CPP_EXAMPLE_H  
extern "C" int add( int x, int y );  
#endif  
//C++实现文件 cppExample.cpp  
#include "cppExample.h"  
int add( int x, int y )  
11
{  
  return x + y;  
}  
/* C 实现文件 cFile.c  
/* 这样会编译出错:#include "cExample.h" */  
extern int add( int x, int y );  
int main( int argc, char* argv[] )  
{  
  add( 2, 3 );   
  return 0;  
}  
如果深入理解了第 3 节中所阐述的 extern "C"在编译和连接阶段发挥的作用,就能真正理解本节
所阐述的从 C++引用 C 函数和 C 引用 C++函数的惯用法。对第 4 节给出的示例代码,需要特别留意各个
细节。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:11 | 显示全部楼层
C 语言高效编程的几招
编写高效简洁的 C 语言代码,是许多软件工程师追求的目标。本文就工作中的一些体会和经验做相关的阐述,不对的地方

各位指教。
第1 招:以空间换时间
计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决
问题
的第1 招-- 以空间换时间。
例如:字符串的赋值。
方法A,通常的办法:
#define LEN 32
char string1 [LEN];  
memset (string1,0,LEN);
strcpy (string1,"This is an example!!"  
方法B:
const char string2[LEN]="This is an example!"
char*cp;
cp=string2;  
( 使用的时候可以直接用指针来操作。)
从上面的例子可以看出,A 和B 的效率是不能比的。在同样的存储空间下,B 直接使用指针就可以操作了,而 A 需要调用
两个字符函数才能完成。B 的缺点在于灵活性没有 A 好。在需要频繁更改一个字符串内容的时候,A 具有更好的灵活性;
如果采用方法 B,则需要预存许多字符串,虽然占用了  大量的内存,但是获得了程序执行的高效率。
如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。
12
该招数的边招-- 使用宏函数而不是函数。举例如下:
方法C:
#define bwMCDR2_ADDRESS 4  
#define bsMCDR2_ADDRESS 17
int BIT_MASK (int_bf)
{
return ((IU<<(bw##_bf))-1)<<(bs##_bf);
}
void SET_BITS(int_dst,int_bf,int_val)  
{
_dst=((_dst) & ~ (BIT_MASK(_bf)))I\  
(((_val)<<<(bs##_bf))&(BIT_MASK(_bf)))  
}
SET_BITS(MCDR2,MCDR2_ADDRESS,RegisterNumb
er);  
方法D:
#define bwMCDR2_ADDRESS 4  
#define bsMCDR2_ADDRESS 17
#define bmMCDR2_ADDRESS BIT_MASK
(MCDR2_ADDRESS)
#define BIT_MASK(_bf)(((1U<<(bw##_bf))-1)<<
(bs##_bf)
#define SET_BITS(_dst,_bf,_val)\
((_dst)=((_dst)&~(BIT_MASK(_bf))) I
(((_val)<<(bs##_bf))&(BIT_MASK(_bf))))  
SET_BITS(MCDR2,MCDR2_ADDRESS,RegisterNumb
er);  
函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函数调用是要使用系统的
栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要
在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。而宏函数不存在这个问
题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏
函数的时候,该现象尤其突出。
D 方法是我看到的最好的置位操作函数,是 ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有
的位操作功能。C 方法是其变体,其中滋味还需大家仔细体会。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:11 | 显示全部楼层

第2 招:数学方法解决问题
现在我们演绎高效 C 语言编写的第二招-- 采用数学方法来解决问题。
数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时候,采用一些数学方**对程序
的执行效率有数量级的提高。
举例如下,求 1~100的和。
方法E  
int I,j;
方法F
int I;  
13
for (I=1; I<=100; I++){  
j+=I;  
}
I=(100*(1+100))/2
这个例子是我印象最深的一个数学用例,是我的饿计算机启蒙老师考我的。当时我只有小学三年级,可惜我当时不知道用
公式Nx(N+1)/2 来解决这个问题。方法E 循环了 100 次才解决问题,也就是说最少用了100 个赋值、100 个判断、200
个加法(I 和j) ;而方法 F 仅仅用了 1 个加法、1 个乘法、1 次除法。效果自然不言而喻。所以,现在我在编程序的时候,
更多的是动脑筋找规律,最大限度地发挥数学的威力来提高程序运行的效率。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:12 | 显示全部楼层
第3 招:使用位操作
实现高效的 C 语言编写的第三招-- 使用位操作,减少除法和取模的运算。
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“ 位运算”来完成所有的运算和操作。一般的位操作
是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。举例台如下:
方法G
int I,J;  
I=257/8;  
J=456%32;  
方法H
int I,J;  
I=257>>3;  
J=456-(456>>4<<4);
在字面上好象 H 比G 麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法 G 调用了基本的取模函数和除法函数,
既有函数调用,还有很多汇编代码和寄存器参与运算;而方法H 则仅仅是几句相关的汇编,代码更简洁、效率更高。当然,
由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的 MS C,ARM C来看,效率的差距还是不小。相关汇编
代码就不在这里列举了。
运用这招需要注意的是,因为 CPU的不同而产生的问题。比如说,在 PC上用这招编写的程序,并在 PC上调试通过,在
移植到一个 16位机平台上的时候,可能会产生代码隐患。所以只有在一定技术进阶的基础下才可以使用这招。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:12 | 显示全部楼层
第4 招:汇编嵌入
高效C 语言编程的必杀技,第四招-- 嵌入汇编。
“ 在熟悉汇编语言的人眼里,C 语言编写的程序都是垃圾” 。这种说法虽然偏激了一些,但是却有它的道理。汇编语言是效
率最高的计算机语言,但是,不可能靠着它来写一个操作系统吧?所以,为了获得程序的高效率,我们只好采用变通的方
法-- 嵌入汇编、混合编程。
举例如下,将数组一赋值给数组二,要求每一个字节都相符。char string1[1024], string2[1024];  
14
方法I
int I;  
for (I=0; I<1024; I++)  
*(string2+I)=*(string1+I)
方法J
#int I;
for(I=0; I<1024; I++)  
*(string2+I)=*(string1+I);  
#else
#ifdef_ARM_
_asm  
{
MOV R0,string1  
MOV R1,string2  
MOV R2,#0
loop:  
LDMIA R0!,[R3-R11]
STMIA R1!,[R3-R11]
ADD R2,R2,#8
CMP R2, #400
BNE loop
}
#endif
方法I 是最常见的方法,使用了 1024 次循环;方法 J 则根据平台不同做了区分,在 ARM平台下,用嵌入汇编仅用 128
次循环就完成了同样的操作。这里有朋友会说,为什么不用标准的内存拷贝函数呢?这是因为在源数据里可能含有数据为
0 的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。这个例程典型应用于LCD数据的拷贝过程。根
据不同的 CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。
虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限制了程序的可移植性,使程序在
不同平台移植的过程中,卧虎藏龙、险象环生!同时该招数也与现代软件工程的思想相违背,只有在迫不得已的情况下才
可以采用。切记。
使用C 语言进行高效率编程,我的体会仅此而已。在此已本文抛砖引玉,还请各位高手共同切磋。希望各位能给出更好的
方法,大家一起提高我们的编程技巧。

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:12 | 显示全部楼层
-| endeaver 发表于 2006-3-8 16:16:00
C 语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测
试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。  
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对 ANSI 标准细节的知识而不是技术技巧而
设计吗?这个愚蠢的问题吗?如要你答出某个字符的 ASCII 值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题
者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做这份工作。   
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者 C 语言的水平。不管怎么样,看一下这人如何
回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现
出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。  
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实
际碰到的。其中有些题很难,但它们应该都能给你一点启迪。  
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问
题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。  
预处理器(Preprocessor)

使用特权

评论回复
给力芯片|  楼主 | 2015-5-30 23:13 | 显示全部楼层
1 .  用预处理指令#define 声明一个常数,用以表明 1 年中有多少秒(忽略闰年问题)  
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL  
我在这想看到几件事情:  
•; #define  语法的基本知识(例如:不能以分号结束,括号的使用,等等)  
•; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
  
•; 意识到这个表达式将使一个 16位机的整型数溢出-因此要用到长整型符号 L, 告诉编译器这个常数是的长整型数。  
•; 如果你在你的表达式中用到 UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。  
2 .  写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。  
#define MIN(A,B) ((A) <= (B) ? (A) : (B))  
这个测试是为下面的目的而设的:  
•; 标识#define 在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline) 操作符变为标准 C 的一部分,宏是方便产生嵌入代码的唯一方
法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。  
•; 三重条件操作符的知识。这个操作符存在 C 语言中的原因是它使得编译器能产生比if-then-else 更优化的代码,了解这个用法是很重要的。
  
•; 懂得在宏中小心地把参数用括号括起来  
•; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?  
   least = MIN(*p++, b);  
3. 预处理器标识#error的目的是什么?  
如果你不知道答案,请看参考文献 1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读 C 语言课本的附录去找出象
这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。  
死循环(Infinite loops )

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

65

主题

624

帖子

0

粉丝