1. static
1. 在函数体(静态局部变量
),只会被初始化一次
,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。(限定生命周期
)
int autoAdd(void){
// 静态的局部函数延长声明周期,只在第一次调用时进行初始化
// 使用static修饰的局部变量的作用域依然是函数体的内部。
static int i = 100;
i++;
return i;
}
int main(int argc, const char *argv[]){
printf("%d\n", autoAdd()); // 101
printf("%d\n", autoAdd()); // 102
printf("%d\n", autoAdd()); // 103
return 0;
}
int autoAdd1(void){
// 静态的局部函数延长声明周期,只在第一次调用时进行初始化
// 如果没有初始化,默认初始化为0
int j; // 随机值
static int i;
i++;
return i;
}
int main(int argc, const char *argv[]){
printf("%d\n", autoAdd1()); // 1
printf("%d\n", autoAdd1()); // 2
printf("%d\n", autoAdd1()); // 3
return 0;
}
2. 在模块内(但在函数体外)(静态全局变量
),一个被声明为静态的变量
可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量(只能被当前文件使用,限定作用域
)。
#include <stdio.h>
// 将全局变量使用static修饰声明为一个静态的全局变量
// 此变量只能在本文件中使用其他文件中不可以使用。
static int global = 2000;
// 将函数使用static修饰声明为一个静态的全局函数
// 此函数则只能在本文件中使用,其他外部文件不可以使用。
3. 在模块内,一个被声明为静态的函数
只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用
)。
1> static修饰局部变量,延长变量的生命周期到整个程序,
static修饰的局部变量如果初始化,则存到.data(初始化的全局数据段)段;
static修饰的局部变量如果没有初始化,则存到.bss(未初始化的全局数据段)段,默认初始值为0.
static修饰的局部变量只在函数第一次调用时被初始化一次,
后边如果在调用这个函数则不再进行初始化。、
2> static修饰全局变量,表示这个全局变量不可以在其他文件中使用。
3> static修饰函数,表示这个函数在其他源文件中不可以被调用。
2.volatile
volatile ----> 易变的
- 防止编译器对代码进行优化的,每次读取变量的值时都是从变量的地址中读值
(内存中读)
,而不是从缓冲区
中获取值。 - volatile关键字在底层的代码封装时会频繁的使用。
以下转载:知乎仲一
1. 并行设备的硬件寄存器。
存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。
当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地址的数据进行假设。
2. 一个中断服务程序中修改的供其他程序检测的变量。
volatile提醒编译器,它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中
读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的
值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
3. 多线程应用中被几个任务共享的变量。
单地说就是防止编译器对代码进行优化.比如如下程序:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述
四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入
volatile,编译器会逐一的进行编译并产生相应的机器代码(产生四条代码)。
3. const 只读
- const修饰的变量是
只读
的变量(不是常量),const修饰的变量在.rodata段 或者 在.stack(栈)中存放
- const 全局变量 存储在只读数据段(.rodata段);
- const 局部变量 存储在
栈
中,代码块结束时释放
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buf[4];
const int a=0;
buf[4]=97;
printf("a=%d\n",a);
//Vscode会报错:*** stack smashing detected ***
//已放弃 (核心已转储)
//但是QT4.11不会,而且a=97;
//说明:
/*
const修饰的变量,其实质是告诉程序员或编译器该变量为只读,如果程序员在程序中显示的修改一个只读变量,
编译器会毫不留情的给出一个error。而对于由于像数组溢出,隐式修改等程序不规范书写造成的运行过程中的修改,
编译器是无能为力的,也说明const修饰的变量仍然是具备变量属性的。
也就是 const修饰的变量是只读的变量(不是常量),const修饰的变量在.rodata段或者在.stack(栈)中存放
*/
return 0;
}
- 如果给
用const修饰
的 返回值类型是指针,那么函数返回值(即指针)的内容是不能被修改的,
而且这个返回值只能
赋给被 const修饰的指针。
#include <stdio.h>
const char *GetString(void)//定义一个函数
{
static int a=5;
char *p=(char *)&a;
return p;
}
int main(int argc, char const *argv[])
{
#if 0
char *str= GetString(); // 报警告,因为str没有被 const修饰
printf("*str=%d\n",*str);
*str=6; // 修改成功
printf("*str=%d\n",*str); // *str=6
#endif
#if 1
const char *str=GetString(); //正确,对返回[const char *]指针的值 只能读
printf("*str=%d\n",*str);
*str=6; // 报错,不可修改返回[const char *]指针的值
printf("*str=%d\n",*str);
#endif
return 0;
}
C++中一个const不是必需创建内存空间,而在C中,一个const总是需要一块内存空间
C++中对于局部的const变量要区别对待:
- 对于基础数据类型,也就是
const int a = 10
这种,编译器会把它放到符号表
中,不分配内存,当对其取地址时,会分配内存 - 对于基础数据类型,如果用一个变量 初始化const变量,如果
const int a = b
,那么也是会给a分配内存 - 对于自定数据类型,比如类对象,那么也会分配内存。
C++中全局const变量
在c++中是否要为const全局变量分配内存空间,取决于这个const变量的用途
- 如果是充当着一个值替换(即就是将一个变量名替换为一个值
const int a = b
),那么就不分配内存空间, - 不过当对这个const全局变量取地址 或者 使用extern时,会分配内存,存储在只读数据段。也是不能修改的。
参考:c语言中const修饰的到底是常量还是变量?
参考:const修饰的变量的存储位置
参考:const、static变量存放位置注意全局和局部变量的CONST,const全局有的编译器直接当立即数存在ROM中
4.extern声明
- 声明变量或者函数是其他.c文件中的,在声明的时候变量不能再被赋初值了。
- extern修饰全局的变量—> 表示这个变量是在其他文件中进行定义的
- extern修饰全局的函数—> 表示这个函数实在其他文件中进行定义的
函数本身就是全局的,即使不加extern此函数依然可以被其他文件调用。 - test.c
int a=5;
other()
{
int b=3;
static int c=2;
a+=5; b+=5; c+=5;
printf("%d,%d,%d\n",a,b,c);
c=b;
}
- main.c
int main(void)
{
extern int a;
int b=0;
static int c;
a+=3;
other(); //13 8 7
b+=3;
other(); //18 8 13
}
5.register 寄存器类型的变量
- 在实际开发中尽量不要大量的使用register类型的变量,原因芯片内部就寄存器的个数有限。
- 寄存器类型的变量不可以进行取地址的操作,原因是寄存器没有地址 ,取地址是取内存的地址
#include <stdio.h>
int main(int argc, const char *argv[])
{
// 1. 定义register类型的变量
register int r = 1000;
printf("r = %d\n", r);//1000
// 寄存器类型的变量不可以取地址
printf("&r = %p\n", &r); // error 错误
return 0;
}
6.auto 自动类型
自动类型(auto) : 一般局部变量为自动类型,定义局部变量时加auto和不加auto效果一样
非自动类型:全局变量:使用static修饰的局部变量, 使用static修饰的全局变量不能是auto类型。
局部变量是否使用auto修饰都是自动类型
#include <stdio.h>
// 全局变量
int a = 100;
// auto int b = 200; // error 不可以使用auto修饰
// auto static int c = 200; // error 不可以使用auto修饰
int main(int argc, const char *argv[])
{
// 局部变量
int d = 300;
auto int e = 400; // 正确
// auto static int f = 500; // error 不可以使用auto修饰
return 0;
}
栈stack
- 4种栈结构:
- 空栈:
栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;
而取出时需要先移动一格才能取出
- 满栈:
栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;
取出时可以直接取出,然后再移动栈指针
- 增栈:
栈指针移动时向地址增加的方向移动的栈;
向上拓展↑
- 减栈:
栈指针移动时向地址减小的方向移动的栈;
向下拓展↓
- ARM种的4种栈结构:
- 满减栈:
进栈(先移动指针再入栈,指针往地址减小的方向移动);
出栈(先出栈,栈指针往地址增大的地方移动)。
- 满增栈:
进栈(先移动指针再入栈,指针往地址增大的方向移动);
出 栈(先出栈,栈指针往地址减小的地方移动)。
- 空减栈:
进栈(先进栈,栈指针往地址减小的方向移动);
出栈(先移动指针再出栈,栈指针往地址增大的方向移动)
- 空增栈:
进栈(先进栈,栈指针往地址增大的方向移动);
出栈(先移动指针再出栈,栈指针往地址减小的方向移动)
备注:
使用时不用纠结时哪一种栈结构,只要进栈和出栈用同一种栈结构就不会出错。
ARM默认使用满减栈