const关键字
C语言中const修饰通常是用来声明常量,并声明常量的值不能修改。当涉及指针变量时情况就会变得更加有趣,需要特别注意。因为有两样东西都有可能成为常量—一种是用来限定指向的空间的值不可修改;另一种是限定指针不可修改。下面是几个声明的例子:
int *pi; //pi是一个普通的指向整型的指针变量
int const *pci; //const修饰的是指针指向的值,故不可修改它所指向的值
int *const cpi; //const修饰的是指针,故不可修改指针
总结:const 修改的是数据类型是指针,则指针可修改;类型是变量,则变量可修改。
概念
在了解static关键字的用法之前,先需要了解C语言中的作用域、连接属性和存储类型的概念。
作用域
当变量在程序的某个部分被声明时,它只有在程序一定区域才能被访问。这个区域由标识符(变量)的作用域决定。
编译器可以确认4种不同类型的作用域–文件作用域、函数作用域、代码块作用域和原型作用域。标识符声明的位置决定它的作用域。
代码块作用域
位于一对花括号({})之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符(变量)都具有代码块作用域(block scope)。
int a;
int d(int e)
{
int f;
int g(int h);
{
int f,g,i; // 当程序执行到这里,访问的是刚刚声明的f的变量
}
// 当程序执行到这里时,访问的是本代码块({})的变量f的值
}
文件作用域
任何在代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都是可以访问的。
原型作用域
原型作用域只适用于在函数原型中声明的参数名。如int b(int c):参数c的作用域–原型作用域。
函数作用域
函数作用域简化为一条规则–一个函数中所有语句标签必须唯一。
链接属性
当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行程序。然而,标识符的链接属性决定如何处理在不同文件中出现的标识符。标识符的作用域与它的链接属性有关,但这两个属性并不相同。
连接属性一共有3种-----external(外部)、internal(内部)和nohe(无)。
关键字external和static用于在声明中修改标识符的链接属性。如果某个声明在正常情况下具有external链接属性,在它前面上加上static关键字可以使它的链接属性变为internal。例如,如果下面这个声明这样书写:
static int b;
那么这个变量b就将为这个源文件私有。
注意:static 只对缺省值链接属性为external的声明才有改变链接属性的效果。
储存类型
变量的存储类型是指储存变量值的内存类型。变量的储存类型决定变量何时创建、何时销毁以及它的值将保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。
变量的缺省存储类型取决于它的声明位置。凡是在任何代码块之外明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。
在代码块内部声明的变量的缺省储存类型是自动的(automatic),也就是说它存储于堆栈中,称为自动变量。对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变量变为静态。注意:修改变量的储存类型并不表示修改该变量的作用域,它仍然只能在该代码块的内部按名字访问。
关键字register用于自动变量的声明,提示它们应该储存于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。
static关键字
当用于不同的上下文环境时,static关键字具有不同的意见。很不幸的是给予程序新手带来混淆。本篇文章对static关键字作了总结,再加上后续的程序例子,应该能给予帮助你搞清static关键字的问题。
- 当static用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符存储类型和作用域不受影响。用这种方式声明函数或变量只能在声明它们的源文件中访问。
- 当static用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,自动变量修改为静态变量,但变量的链接属性和作用域不受影响。
int a = 5;
extern int b;
static int c;
int function(int e)
{
int f = 15;
register int b;
static int g;
extern int a;
{
int e;
int a;
extern int h;
}
{
int x;
int e;
}
}
static int prifunction(void)
{
}
变量a、b、c的存储类型为静态,表示它们并不是存储于堆栈中。
函数function的作用域从函数开始到文件结束。
参数e不具备链接属性,只能通过函数内部访问。它具备自动存储类型。由于与局部变量冲突,它的作用域是除了冲突e代码块之外的作用域。
变量f、b、g声明局部变量,所以它们的作用域到函数结束为止。它们不具备连接属性,所以它们不能在函数的外部通过名字访问。g的存储类型是静态,所以它在程序的整个执行过程一直存在。
代码块中a、e声明为局部变量。它们具备自动存储类型,不具有链接属性,它们的作用域只在花括号{}内。
全局变量h在代码块内可以被访问。它具有external属性,存储于静态内存中。
代码块变量x、e创建局部变量(自动、无链接属性、作用域限于本代码块)
函数prifunction具有静态链接属性。静态链接属性可以防止它被这个源文件之外的任何函数调用。
volatile的作用
最重要的作用及示例:告诉编译器不能做任何优化。
在嵌入式或单片机中,如果用volatile,compiler就不允许做任何的优化,从而保证程序的原意,这对嵌入式来讲非常重要。
1. 易变的
变量保存在内存中,但为了提高运行速度,有时不从内存取值,而从cpu缓存/寄存器中取值。在嵌入式/或单片机开发中,这有时是很致命的。
volatile 定义变量后:假设有读、写两条语句,依次对同一个 volatile 变量进行操作,那么后一条的读操作不会直接使用前一条的写操作对应的 volatile 变量的寄存器内容,而是重新从内存中读取该 volatile 变量的值。
```
volatile int nNum = 0; // 将nNum声明为volatile
int nSum = 0;
nNum = FunA(); // nNum被写入的新内容,其值会缓存在寄存器中
nSum = nNum + 1; // 此处会从内存(而非寄存器)中读取nNum的值
```
2. 不可优化的
在 C/C++ 编程语言中,volatile 的第二个特性是“不可优化性”。
volatile 会告诉编译器,不要对 volatile 声明的变量进行各种激进的优化(甚至将变量直接消除),从而保证程序员写在代码中的指令一定会被执行。
上述描述的(部分)示例代码内容如下:
volatile int nNum; // 将nNum声明为volatile
nNum = 1;
printf("nNum is: %d", nNum);
在上述代码中,如果变量 nNum 没有声明为 volatile 类型,则编译器在编译过程中就会对其进行优化,可能直接使用常量“1”进行替换(这样优化之后,生成的汇编代码很简洁,执行时效率很高)。
而当使用 volatile 进行声明后,编译器则不会对其进行优化,nNum 变量仍旧存在,编译器会将该变量从内存中取出,放入寄存器之中,然后再调用 printf() 函数进行打印。
3. 顺序执行的
在 C/C++ 编程语言中,volatile 的第三个特性是“顺序执行特性”,即能够保证 volatile 变量间的顺序性不会被编译器进行乱序优化。
C/C++ 编译器最基本优化原理:保证一段程序的输出,在优化前后无变化。为了对本特性进行深入了解,下面以两个变量(nNum1 和 nNum2)为例(既然存在“顺序执行”,那描述对象必然大于一个),介绍 volatile 的顺序执行特性,示例代码内容如下:
int nNum1;
int nNum2;
nNum2 = nNum1 + 1; // 语句1
nNum1 = 10; // 语句2
在上述代码中:当 nNum1 和 nNum2 都没有使用 volatile 关键字进行修饰时,编译器会对“语句1”和“语句2”的执行顺序进行优化:即先执行“语句2”、再执行“语句1”;
当 nNum2 使用 volatile 关键字进行修饰时,编译器也可能会对“语句1”和“语句2”的执行顺序进行优化:即先执行“语句2”、再执行“语句1”;
当 nNum1 和 nNum2 都使用 volatile 关键字进行修饰时,编译器不会对“语句1”和“语句2”的执行顺序进行优化:即先执行“语句1”、再执行“语句2”;文章来源:https://www.toymoban.com/news/detail-463375.html
说明:对一般程序来讲保证值一样即可,但对嵌入式来讲,执行顺序很重要,上述可通过观察代码的生成的汇编代码进行验证。文章来源地址https://www.toymoban.com/news/detail-463375.html
到了这里,关于嵌入式C语言关键字(const、static、volitatile)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!