C语言:static,volatile,const,extern,register,auto, 栈stack结构

进程的虚拟空间

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 ----> 易变的

  1. 防止编译器对代码进行优化的,每次读取变量的值时都是从变量的地址中读值 (内存中读) ,而不是从 缓冲区 中获取值。
  2. 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 只读

  1. 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;
}

在这里插入图片描述

  1. 如果给 用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变量要区别对待:

  1. 对于基础数据类型,也就是 const int a = 10 这种,编译器会把它放到 符号表 中,不分配内存,当对其取地址时,会分配内存
  2. 对于基础数据类型,如果用一个变量 初始化const变量,如果 const int a = b ,那么也是会给a分配内存
  3. 对于自定数据类型,比如类对象,那么也会分配内存。

C++中全局const变量

在c++中是否要为const全局变量分配内存空间,取决于这个const变量的用途

  1. 如果是充当着一个值替换(即就是将一个变量名替换为一个值 const int a = b ),那么就不分配内存空间,
  2. 不过当对这个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种栈结构:
  1. 空栈:
栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;
而取出时需要先移动一格才能取出
  1. 满栈:
栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;
取出时可以直接取出,然后再移动栈指针
  1. 增栈:
栈指针移动时向地址增加的方向移动的栈;
向上拓展↑
  1. 减栈:
栈指针移动时向地址减小的方向移动的栈;
向下拓展↓
  • ARM种的4种栈结构:
  1. 满减栈:
进栈(先移动指针再入栈,指针往地址减小的方向移动);
出栈(先出栈,栈指针往地址增大的地方移动)。
  1. 满增栈:
进栈(先移动指针再入栈,指针往地址增大的方向移动);
出 栈(先出栈,栈指针往地址减小的地方移动)。
  1. 空减栈:
进栈(先进栈,栈指针往地址减小的方向移动);
出栈(先移动指针再出栈,栈指针往地址增大的方向移动)
  1. 空增栈:
进栈(先进栈,栈指针往地址增大的方向移动);
出栈(先移动指针再出栈,栈指针往地址减小的方向移动)

备注:
使用时不用纠结时哪一种栈结构,只要进栈和出栈用同一种栈结构就不会出错。
ARM默认使用满减栈

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值