嵌入式C摘录

1. 什么是可重入代码?如何写可重入代码?

可重入就是一个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。可重入代码,必须保证资源的互不影响的使用,比如全局变量,系统资源等。例如:最简单的就是递归了。   
可重入代码,要防止使用全局量,
    
  错误的可重入代码   
  int   *a;   
    
  void   fun(void)   
  {   
          a   =   new   int[20];   
          fun();   
          delete[]a;   
  }   
  a的地址被丢失了。   
    
  正确的可重入代码   
  void   fun(void)   
  {   
          int   *a;   
          a   =   new   int[20];   
          fun();   
          delete[]a;   
  }

==============================================================
2. 写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个?
    #define   MIN(A,B)   ( ((A) <= (B))  ?  (A) : (B))
 这个测试是为下面的目的而设的: 
1). 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变 
为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到 
要求的性能,嵌入代码经常是必须的方法。 
2). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if- 
then-else更优化的代码,了解这个用法是很重要的。 
3). 懂得在宏中小心地把参数用括号括起来 
这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事? 
    least = MIN(*p++, b);

=====================================================================
3. 预处理器标识#error的目的是什么?
指令 ----------------用途 
# ---------------------空指令,无任何效果 
#include --------------包含一个源代码文件 
#define ---------------定义宏 
#undef ----------------取消已定义的宏 
#if --------------------如果给定条件为真,则编译下面代码 
#ifdef -----------------如果宏已经定义,则编译下面代码 
#ifndef ----------------如果宏没有定义,则编译下面代码 
#elif -------------------如果前面的#if给定条件不为真,当前条件为真,则编译下面代码 
#endif ----------------结束一个#if……#else条件编译块 
#error ----------------停止编译并显示错误信息 

================================================================
5. 用变量a给出下面的定义 
a) 一个整型数(An integer) 
b) 一个指向整型数的指针(A pointer to an integer) 
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer) 
d) 一个有10个整型数的数组(An array of 10 integers) 
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers) 
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers) 
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) 
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

答案是: 
a) int a; // An integer 
b) int *a; // A pointer to an integer 
c) int **a; // A pointer to a pointer to an integer 
d) int a[10]; // An array of 10 integers 
e) int *a[10]; // An array of 10 pointers to integers 
f) int (*a)[10]; // A pointer to an array of 10 integers 
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer 
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer 
=====================================================================
6. 关键字static的作用是什么?
在C语言中,关键字static有三个明显的作用: 
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。 
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。 
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

==================================================================
7.关键字const是什么含意? 
只要能说出const意味着“只读”就可以了。这个答案不是完全的答案,将问他一个附加的问题:下面的声明都是什么意思?

1) const int a; 
2) int const a; 
3) const int *a; 
4) int * const a; 
5) int const * const a;

1,2------的作用是一样,a是一个常整型数。
3--------意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。
4--------意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
5--------最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。


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

///////////////////////////////////
1999年2月《Embedded Systems Programming》上刊登的《const T vs. T const》,作者是Dan Saks。
观点1:
      任何一个申明都由两主要部分组成:一个或者几个限定符(declaration specifier)和一序列由逗号隔开的申明变量(declarator)。举个例子:static unsigned long int    *x[N];
其中:static unsigned long int   限定符部分;
          *x[N]       申明变量部分;
申明变量部分是要申明的变量的名字,它可能和* , [] , () , &(for C++)结合起来使用。我们知道,*用于申明表示变量是指针类型;[]意味着数组;()有两种用法,第一种是作为函数调用操作符;另外一种是用作分组符。
       对于上面的例子,x是指向数组(数组元素类型是static unsigned long int)的指针,还是x是一个数组,数组的元素是static unsigned long int* 类型呢?为此,引入观点2。
观点2:
        申明变量中如果有操作符(例如 * [] ),按照表达式运算中的优先级规则进行处理。
        我们知道,在表达式运算中,* 的优先级比 [] 低。同样在申明变量的处理也是如此。如此以来,上面的疑问就可以解决了。我们看declarator部分:*x[N];由于[]的优先级比较高,所以x是 一个数组在x是一个指针之前。如此以来,*就用来修饰数组元素了。
        对于(),如果用作函数调用,它得优先级和[]一样;如果是用于分组作用,它得优先级最高。
观点3:
      对于变量申明限定符部分,可能有类型限定符,还可能有非类型限定符(例如:static , extern , virtual)。类型限定符只直接作用于申明体(申明的变量)的类型;而非类型限定符直接作用于申明体。
      继续拿前面的例子,x是一个数组,unsigned long int是类型限定符,表示x这个数组的元素类型;而static是非类型限定符,指示x是静态分配内存。
观点4:
        对于观点3,非类型限定符主要是针对static来说的,对于const 和volatile来说,它们是类型限定符。
        举个例子:const void *vectortable[N]
       如果把const当作非类型限定符的话,按照观点3来分析,vectortable是一个数组,const由于是非类型限定符,所以是修饰 vectortable的,于是vectortable是一个指向数组的常量指针,数组元素的类型是void *。事实上不是这样,const是类型限定符,修饰变量vectortable的类型的,这样vectortable是一个指向数组的指针,数组元素类型 是const void *。
观点5:
        限定部分的各个限定词之间的前后顺序没关系。
        例如:const   VP   t;   和 VP const T等价
                 const char   *p 等价于 char const   *p;
        说明:大多数资料和程序员都习惯将static非类型限定符放在变量申明的最前面,实际上这仅仅是习惯的问题,并不是语言自身的规定。
观点6:
        一种申明风格:对于限定部分里面的各个类型限定词,如果有const ,最好把const 放在右边而不是左边。尽量使用 T const 代替 const T,避免错误。
        例如:const   char *p; 我们这样写: char const *p。之所以这样做,是为了可读性。注意这个可读性是针对人的,而不是针对编译器的。前面观点5说了,编译器不区分这个顺序。下面我们看看这样书写风格怎 样达到可读性好的效果。
T const *p : 从右往左读(*作为分隔符,标记指针的):p是指针,指向const T;
等价于:const T *p;
T * const p : 从右往左,常量指针指向T
由上可见方便之处了。我们只要按照从右往左边顺序就可以读出来申明的意义。
除此之外,这样写还不会出错。我们看看下面的一个例子。
typedef int *IP;
int a = 3;
const IP t = &a;
此时t是啥类型呢?
按照以前风格,我们替换IP为 int * 得到:const int * t;如此一来,等价于int const * t;也就是说t是指向常整形指针。实际上是否是这样的呢?
答案是否定的。实际上t是指向整形的常指针。正确的理解是代替IP用如下方式:
IP const t
int *const t;
因此,在申明变量的时候,将const放在限定部分的最右边是一种比较好的做法。例如上面对变量t的申明:IP const t;这样保证程序员不会对t的类型产生误解。
注意:如果你非要使用typedef来实现const int   *t,那么就直接typedef const int *CIP;然后:CIP   t;就可以了。

===================================================================
8. 关键字volatile有什么含意 并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: 
1). 并行设备的硬件寄存器(如:状态寄存器) 
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 
3). 多线程应用中被几个任务共享的变量 
嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。 
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。 
1). 一个参数既可以是const还可以是volatile吗?解释为什么。 
2). 一个指针可以是volatile 吗?解释为什么。 
3). 下面的函数有什么错误: 
int square(volatile int *ptr) 

return *ptr * *ptr; 

下面是答案: 
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码: 
int square(volatile int *ptr) 

int a,b; 
a = *ptr; 
b = *ptr; 
return a * b; 

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下: 
long square(volatile int *ptr) 

int a; 
a = *ptr; 
return a * a; 
}

==============================================================
9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。

对这个问题有三种基本的反应 
1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。 
2). 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。
3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下: 
#define BIT3 (0x1<<3) 
static int a; 
void set_bit3(void) 

a |= BIT3; 

void clear_bit3(void) 

a &= ~BIT3; 

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。

==============================================================

10.在C语言中打印出当前所执行的行号?

printf("__LINE/n");

==============================================================

11. 将一个int型a 的第9位置1,将a的第9位置0;

a |=      (1<<9);  /*第9位置1*/

a &= ~(1<<9);  /*第9位置0*/

===============================================================

12. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒
    #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
                                                       UL(表示无符号长整型)

13. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

方案一:

while(1)

{

}

 

方案二:

for(;;)

{

}

方案三:

LOOP:

{

}

goto LOOP

从这你能看出什么?

===========================================================

14.不要对shor 和 char 进行register定义

曾经我的一大师给日本人做项目时,用的OS是linux,在键盘去抖动时做了这样的延时:

for(i = 0; i < 3; i++)

{

;

}

在实验室测试是OK的,没有丢键。可是拿到实际环境中,每天丢2次键,

这是为什么呢?驱动没有做好吗?重写了驱动还是不行,结果把原来的

程序做了这样的改动就OK了。

i = 1;

i = 1;

i = 1;

从这你又能看出什么呢?

=========================================================

15.char str[4] = {0x00, 02, 0x03, 0x04};

   1)、*(int *)str = ? 

   大端:0x00020304

   小端:0x04030200

   2)、02 是几进制? 

    8进制

=========================================================

16.怎么样处理内存泄露?

方案一:

ntrnce();  /*启动跟踪*/

申请空间代码

...........

...........

untrnce(); /*停止跟踪,并处里内存*/

方案二:

dmalloc工具,它对malloc进行二次封装,在退出时

进行内存检查,使用这个工具时需要相关包括头文件

发布了23 篇原创文章 · 获赞 10 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章