十九、存储类型
- 存储类型:auto(自动)、static(静态)、extern(外部)、register(寄存器)
- 修饰词:const(不允许被修改)、volatile(防止内存被编译器优化)
19.1 auto
作用:声明自动类型,当局部变量省略存储类型时,是自动类型
- auto不可以修饰全局变量
- 函数只能使用 static / extern 进行修饰,不可以使用 auto 修饰
- auto修饰的变量内存在栈区
19.2 static
作用:声明静态类型,延长生命周期。
static
关键字修饰:
- 全局变量:当
static
用于修饰全局变量时,它使得该变量的作用域限制在当前文件内,即使其他文件声明了同名的全局变量,它们也不会相互影响。 - 局部变量:当
static
用于修饰局部变量时,它使得该变量的生命周期延长,即变量在程序执行期间保持其值,而不是在每次函数调用时重新初始化。这在递归函数中特别有用,不能跨过作用域使用。 - 指针:
static
修饰指针本身并没有特殊意义,它与普通指针的行为没有区别。但是,如果将static
用于指针指向的变量,那么该变量的生命周期将会延长,类似于上述局部变量的效果。static修饰的指针不可以指向自动类型的变量地址。 - 函数:当
static
用于修饰函数时,它将使函数的作用域限制在当前文件内,其他文件无法访问该函数,起到了函数的私有化效果。 - 线程局部变量(Thread-local variables):
C11标准引入了线程局部变量的概念,通过static关键字与关键字_Thread_local一起使用,可以在多线程程序中创建独立于线程的局部变量。每个线程都拥有其独立的变量实例,互不干扰。
代码示例:
- 1.static修饰全局变量
全局变量省略存储类型,默认是extern存储类型,全局变量的内存空间在静态区。
一般全局变量不会使用static修饰,原因是全局变量省略存储类型,内存已经在静态区.
- 2.static修饰局部变量
作用:static修饰局部变量,延长生命周期,不是作用域、
生命周期 : 内存空间的神情,到释放
作用域 : 在程序的有效范围
- 3.static修饰函数
作用:static修饰函数,使函数声明周期在本文件有效,不可以跨文件调用
函数省略存储类型,默认是extern存储类型
- 4.static修饰指针
static修饰的指针不可以指向自动类型的变量地址
原因 : 自动类型的变量地址后分配,在编译阶段分配空间
静态类型的变量地址先分配, 在项目启动时就分配空间
19.3 extern
作用:声明外部存储
extern
关键字声明:
- 全局变量:当在一个文件中使用
extern
修饰全局变量时,表明该变量是在其他文件中定义的全局变量。这样可以在当前文件中使用其他文件定义的全局变量,从而实现多文件共享全局变量的目的。 - 局部变量:
extern
不应用于局部变量,因为局部变量的作用域仅限于定义它的代码块内,无需在其他文件中访问。 - 指针:
extern
同样也可以用于指针,但是它的效果与全局变量的情况类似,表示该指针是在其他文件中定义的。 - 函数:
extern
不应用于函数,因为函数的作用域是全局的,无需通过extern
来在其他文件中访问。
- 当前全局变量省略存储类型,默认是extern
- 当前全局变量省略存储类型,默认是extern
- extern修饰的变量,存储在静态区
总结:
static
主要用于限制作用域和延长生命周期。extern
主要用于在多文件项目中共享全局变量。
19.4 register
作用:声明寄存器变量,
寄存器类型的变量,访问速度快,但不可以取地址
存储器等级:寄存器 > 高速缓冲存储器cache > 主存 > 辅存
19.5 const
作用:修饰的值不发生改变
1. const修饰的全局变量,内存在静态区的只读段,只读不可以修改
2. const修饰的局部变量,内存在栈区,不可以修改
3. const和指针结合:
const int *p; *在const的右边,const修饰的值,值不可以改变,地址可以改变
int const *p;*在const的右边,const修饰的值,值不可以改变,地址可以改变
int * const p;*在const的左边,const修饰的地址,不地址可以改变,值可以改变
const int * const p;第一个const修饰的是指,第二个const修饰的地址,值和地址均不可以改变
int const * const p; 第一个const修饰的是指,第二个const修饰的地址,值和地址均不可以改变
19.6 volatile
作用:防止内存优化,保存内存可见性
在C/C++中,volatile
是一个关键字,用于告诉编译器该变量可能在程序执行过程中被意外地更改,从而防止编译器对该变量进行优化,以确保程序的正确性。volatile
的主要用途是在以下情况下:
-
防止编译器优化:
编译器在优化代码时会尽可能地减少对变量的读取和写入,以提高程序的执行效率。但是,有些变量的值可能不是由当前代码段控制的,而是由硬件或其他并发线程改变的。使用volatile
关键字可以告诉编译器不要对该变量进行优化,而是每次都从内存中读取其最新值。 -
与中断处理相关:
在嵌入式系统和多线程环境中,中断可能会修改程序中的变量。如果这些变量没有被声明为volatile
,编译器可能会优化掉对这些变量的读取操作,导致程序出现错误。 -
与外设交互:
在嵌入式系统中,与外设进行交互时,使用volatile
关键字可以确保对外设寄存器的访问不被优化,从而确保与外设的正确通信。
请注意:volatile
关键字只是告诉编译器该变量可能在任意时刻被修改,但它并不能解决所有多线程并发访问的问题。对于多线程并发的情况,更复杂的同步机制(如互斥锁)通常需要使用。在多线程环境中,volatile
关键字仅仅是一个保证该变量不被优化的手段,而并不能保证线程安全。
二十、宏
宏 : 一旦定义不可以修改
定义格式 : #define 宏名 宏体
宏制作替换、不做计算、不做正确性检查
宏不属于C语句,所以不加分号
20.1 宏定义
#define M 100
#define PAI 3.14
#define CH 'A'
#define STRING "hello"
#define N
#define G =100
// 上面这些都是宏
int main(int argc, const char *argv[])
{
printf("M=%d\n",M);
printf("PAI=%f\n",PAI);
printf("CH=%c\n",CH);
printf("STRING=%s\n",STRING);
// N=6; //一旦宏定义没有赋值,那么后期就不可以赋值
printf("N=%d\n",N);
// printf("G=%d\n",M);
return 0;
}
20.2 宏函数
20.2.1 自定义宏函数
定义格式 : #define 宏函数名(参数列表) 宏体
#define : 定义宏的标志,不可以省略
宏函数名 : 满足命名规范,一般大写
参数列表 : 不加数据类型
宏体 : 实现的程序,不加{}
代码示例:
1> 带括号的宏函数
#define SUM(x,y) x+y
#define SUM1(x,y) (x+y)
#define MIN(x,y) x>y?y:x
int main(int argc, const char *argv[])
{
#if N
int a=10,b=100;
printf("和=%d\n",SUM(a,b)/2);//x+y/2=60
printf("和=%d\n",SUM1(a,b)/2);//(10+100)/2=55
printf("%d",MIN(a,b));
#endif
20.2.2 系统宏函数
#if 宏名
C语句1;
#else
C语句2;
#endif
当宏名条件为真,则执行C语句1,否则执行C语句2
#if 0作用注释
#ifdef 宏名
C语句1;
#else
C语句2;
#endif
当宏名已经定义了,则执行C语句1,否则执行那个C语句2
#ifndef 宏名
C语句1;
#else
C语句2;
#endif
当宏名没有定义,则执行那个C语句1,否则执行那个C语句2;
#undef 宏名: 取消宏