C语言中关键字

关键字
1、Volatile关键字有什么含意 并给出三个不同的例子?
一个定义为volatile的变量说明这变量可能会被改变,这样编译器不会对这个变量作优化。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: 
1). 并行设备的硬件寄存器(如:状态寄存器) 
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 
3). 多线程应用中被几个任务共享的变量 
这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求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; 
}

2、关键字const有什么含意?
1)只读。
2)使用关键字const也许能产生更紧凑的代码。
3)使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)

如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
onst int *a;
int * const a;
int const * a const;
前两个的作用是一样,a是一个常整型数。
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。

如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
?; 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
?; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
?; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。关键字const有什么含意?

3、关键字cregister有什么含意?
使用cregister关键字,当我们定义的该类型的对象与C28x的标准的控制寄存器匹配时,编译器会自动产生相关的代码去控制对应的寄存器,使得我们可以在高级编程语言C/C++中对寄存器进行控制;如果不匹配则产生编译器错误。目前可匹配此类型的寄存器包括:
IER:中断使能寄存器
IFR:中断标志寄存器

其定义方式为;
extern cregistervolatile unsigned int IFR;
extern cregistervolatile unsigned int IER;

cregister类型只能对整形或者指针类型进行定义,并且只在本文件的作用域内生效,它既不能在函数内定义,也不能被用在浮点类型、结构体或者共同体类型上面。如果cregister类型定义的变量是可以被外部控制修改的,那么该变量也必须同时使用volatile类型进行声明。

在定义了寄存器之后,我们就可以直接使用寄存器的名字了,但是还有以下的限制(如果不按照规范来,则会有“Illegal use of control register”的错误提示):
1)IFR是不能直接写的,它的置位操作只能通过“或”操作(操作符是|)进行修改,且操作数必须是立即数,它的复位操作只能被“与”操作(操作符是&)进行修改,例如:
IFR |= 0x4;
IFR &= 0x0800
2)IER寄存器除了通过“或”操作或者“与”操作进行修改之外,也可直接赋值,例如:
IER = x;
IER |= 0x100;
printf("IER =%x\n", IER);

4、关键字restrict有什么含意?
C99中新增加了restrict修饰的指针,由restrict修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由restrict修饰的指针表达式中。

由restrict修饰的指针主要用于函数形参,或指向由malloc()分配的内存空间。restrict数据类型不改变程序的语义。编译器能通过作出restrict修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。

restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码.如
int *restrict ptr;
ptr指向的内存单元只能被 ptr 访问到,任何同样指向这个内存单元的其他指针都是未定义的,直白点就是无效指针。

restrict 的出现是因为 C 语言本身固有的缺陷,C 程序员应当主动地规避这个缺陷,而编译器也会很配合地优化你的代码.
例子 :
int ar[10];
int * restrict restar=(int *)malloc(10*sizeof(int));
int *par=ar;
for(n=0;n<10;n++)
{   par[n]+=5;

restar[n]+=5;
ar[n]*=2;
par[n]+=3;
restar[n]+=3;
}

因为restar是访问分配的内存的唯一且初始的方式,那么编译器可以将上述对restar的操作进行优化:restar[n]+=8;而par并不是访问数组ar的唯一方式,因此并不能进行下面的优化:par[n]+=8;因为在par[n]+=3前,ar[n]*=2进行了改变。使用了关键字restrict,编译器就可以放心地进行优化了。

关键字restrict有两个读者。
一个是编译器,它告诉编译器可以自由地做一些有关优化的假定。另一个读者是用户,他告诉用户仅使用满足restrict要求的参数。一般,编译器无法检查您是否遵循了这一限制,如果您蔑视它也就是在让自己冒险。

5. 关键字static的作用是什么?
在C语言中,关键字static有三个明显的作用:
?;一旦声明为静态变量,在编译时刻开始永远存在,不受作用域范围约束,但是如果是局部静态变量,则此静态变量只能在局部作用域内使用,超出范围不能使用,但是它确实还占用内存,还存在.
?;在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
?;在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,很少人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

6. 关键字far的作用是什么?
默认情况下,C/C++的编译器只支持到低64K的存储空间,且所有的指针都默认为16位的。但是C28x的存储空间一般都在16bit以上,此时通过使用far类型,C代码中的指针可以为22bit宽(需要两个存储单元来存储),并支持对高达4M的存储空间的存取。(在C++中,不支持far关键字,对高地址的存取是通过使用在编译器选项中开启large memory model选项实现的。)

当一个变量被定义为far类型时,它被存储在高于64K的地址范围中,此时far类型的全局变量不再保存在.bss段中,而是保存在一个新的段,即.ebss中,相同的道理,far类型的const变量也被保存到.econst段中。注意:只有全局变量和静态变量可以被定义为far类型,函数中的非静态变量(自动存储对象)因为被分配到栈中,被自动当near类型来处理。对于结构体,如果结构体被声明为far类型,则全部成员都会自动继承为far类型。举例如下;
int far *ptr; // 指针指向far类型的int,但是指针本身是near类型的
int * far ptr; // 指针指向near类型的int,但是指针本身是far类型的
int far * far ptr;//指针和指向的内容都是far类型的
int far*memcpy_ff(far void *dest, const far void *src, int count);// 函数的参数为两个far类型的指针,且返回值也为far类型的指针
int *far func();//错误:far类型只能用于数据,不能用于函数
//因为程序地址空间本身就是22位的,最后需要注意的是,目前对于两个far类型指针相减的操作,其结果是16位的指针。

7. 关键字_interrupt的作用是什么?
_interrupt用来声明一个函数是中断处理函数;在严格的ANSIC/C++模式下,也可以使用interrupt关键字来代替。中断处理函数要遵循特殊的寄存器保存规则和退出顺序,从而保证代码的安全。在C/C++程序中产生中断时,所有被中断子程序使用,或者被中断子程序调用的函数使用的状态都需要被保留。此外,_interrupt定义的函数不能有参数,也没有返回值,即:
_interrupt voidint_handler()
{
unsigned int flags;
...
}
唯一特殊的是c_int00函数,它是C/C++程序的入口点,被系统保留为默认的复位中断函数,并在中调用main函数。因为c_int00函数不被任何函数所调用,所以它不需要保存任何状态(毕竟是在复位和初始化状态)。在DSP/BIOS和SYS/BIOS HWI对象中,不需要使用_interrupt关键字,因为Hwi_enter/Hwi_exit宏和Hwi解包器已经包含了该函数,此时使用_interrupt关键字会产生负面的效果。

8. 关键字inline的作用?
 在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。为了解决这个问题,特别的引入了inline修饰符,表示为内联函数。它是一种用空间换取效率的一种手段。栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

下面我们来看一个例子:
#include <stdio.h> 
//函数定义为inline即:内联函数 
inline char* dbtest(int a)

    return (i % 2 > 0) ? "奇" : "偶"; 
}  
int main() 
{  
    int i = 0; 
    for (i=1; i < 100; i++) { 
       printf("i:%d    奇偶性:%s /n", i, dbtest(i));
    }
}
上面的例子就是标准的内联函数的用法,使用inline修饰带来的好处我们表面看不出来,其实在内部的工作就是在每个for循环的内部任何调用dbtest(i)的地方都换成了(i%2>0)?"奇":"偶"这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。

其实这种有点类似咱们前面学习的动态库和静态库的问题,使 dbtest 函数中的代码直接被放到main 函数中,执行for 循环时,会不断调用这段代码,而不是不断地开辟一个函数栈。

内联函数的编程风格
1、关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
如下风格的函数Foo 不能成为内联函数:
inline void Foo(int x, int y); // inline 仅与函数声明放在一起
void Foo(int x, int y)
{
}
而如下风格的函数Foo 则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
{
}
所以说,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了inline 关键字,但我认为inline 不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

2、inline的使用是有所限制的
inline只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。

慎用内联
内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

以下情况不宜使用内联:
(1) 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2) 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。

总结:因此,将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦.而所以声明跟定义要一致,其实是指,如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定.所以,最好将内联函数定义放在头文件中. 
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值