• 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. 在模块内(但在函数体外)(静态全局变量),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量(只能被当前文件使用,限定作用域)。

    #include 
    // 将全局变量使用static修饰声明为一个静态的全局变量
    // 此变量只能在本文件中使用其他文件中不可以使用。
    static int global = 2000;
    // 将函数使用static修饰声明为一个静态的全局函数
    // 此函数则只能在本文件中使用,其他外部文件不可以使用。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用)。

    1> static修饰局部变量,延长变量的生命周期到整个程序,
       static修饰的局部变量如果初始化,则存到.data(初始化的全局数据段)段;
       static修饰的局部变量如果没有初始化,则存到.bss(未初始化的全局数据段)段,默认初始值为0.
       static修饰的局部变量只在函数第一次调用时被初始化一次,
    后边如果在调用这个函数则不再进行初始化。、
    
    2> static修饰全局变量,表示这个全局变量不可以在其他文件中使用。
    3> static修饰函数,表示这个函数在其他源文件中不可以被调用。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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,编译器会逐一的进行编译并产生相应的机器代码(产生四条代码)。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3. const 只读

    1. const修饰的变量是 只读 的变量(不是常量),const修饰的变量在.rodata段 或者 在.stack(栈)中存放
    • const 全局变量 存储在只读数据段(.rodata段);
    • const 局部变量 存储在 中,代码块结束时释放
    #include 
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    1. 如果给 用const修饰 的 返回值类型是指针,那么函数返回值(即指针)的内容是不能被修改的,
      而且这个返回值 只能 赋给被 const修饰的指针。
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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;   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 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
    }   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.register 寄存器类型的变量

    • 在实际开发中尽量不要大量的使用register类型的变量,原因芯片内部就寄存器的个数有限。
    • 寄存器类型的变量不可以进行取地址的操作,原因是寄存器没有地址 ,取地址是取内存的地址
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    6.auto 自动类型

    自动类型(auto) : 一般局部变量为自动类型,定义局部变量时加auto和不加auto效果一样
    非自动类型:全局变量:使用static修饰的局部变量, 使用static修饰的全局变量不能是auto类型。

    局部变量是否使用auto修饰都是自动类型

    #include 
    // 全局变量 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    栈stack

    • 4种栈结构:
    1. 空栈:
    栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;
    而取出时需要先移动一格才能取出
    
    • 1
    • 2
    1. 满栈:
    栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;
    取出时可以直接取出,然后再移动栈指针
    
    • 1
    • 2
    1. 增栈:
    栈指针移动时向地址增加的方向移动的栈;
    向上拓展↑
    
    • 1
    • 2
    1. 减栈:
    栈指针移动时向地址减小的方向移动的栈;
    向下拓展↓
    
    • 1
    • 2
    • ARM种的4种栈结构:
    1. 满减栈:
    进栈(先移动指针再入栈,指针往地址减小的方向移动);
    出栈(先出栈,栈指针往地址增大的地方移动)。
    
    • 1
    • 2
    1. 满增栈:
    进栈(先移动指针再入栈,指针往地址增大的方向移动);
    出 栈(先出栈,栈指针往地址减小的地方移动)。
    
    • 1
    • 2
    1. 空减栈:
    进栈(先进栈,栈指针往地址减小的方向移动);
    出栈(先移动指针再出栈,栈指针往地址增大的方向移动)
    
    • 1
    • 2
    1. 空增栈:
    进栈(先进栈,栈指针往地址增大的方向移动);
    出栈(先移动指针再出栈,栈指针往地址减小的方向移动)
    
    • 1
    • 2

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

  • 相关阅读:
    git工作流(待续)
    【数据结构】堆的实现及排序
    不必购买Mac,这款国产设计工具能轻松替代Sketch!
    Redis篇---第二篇
    html网页制作期末大作业成品:基于HTML+CSS+JavaScript简洁汽车网站(7页)
    ACCESS实例2 资料管理4——资料管理的报表
    伺服阀放大器控制器放大板
    Android R.fraction
    Linux操作系统-线程同步与互斥
    重要功能丨支持1688API 接口对接一键跨境铺货及采购,解决跨境卖家货源烦恼!
  • 原文地址:https://blog.csdn.net/qq_47355554/article/details/126533057