分享

volatile与const学习笔记

 319peter 2010-10-30

volatile与const学习笔记

2008-08-25 13:54:07.0      来源:嵌入式在线        
关键词:  volatile     const  
1:关于指针长度,字符串长度的问题
2:进程间的同步的方式有几种?
3:什么是可重入代码?如何写可重入代码?
4:printf()等可变函数的实现机理
5:volatile 变量的用途?
6:写一个在双链表中插入节点和删除节点的程序。
7:将一个int型a 的第9位置1,将a的第9位置0;
用预处理指令#define 声明一个常数,用以表明1年中有多少秒
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ( ((A) <= (B)) ? (A) : (B))
我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);


3. 预处理器标识#error的目的是什么? ?????????????????????????????????????
/////////////////
指令 用途
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if……#else条件编译块
#error 停止编译并显示错误信息
///////////////////////
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种
问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。


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是什么含意?
去年Dan Saks已经在他的文章里完全概括了const的所有用法,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * const a;

前两个的作用是一样,a是一个常整型数。
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。
第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
最后一个意味着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;
}
一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多