static and volatile

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

   一:static

在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
(1)先来介绍它的第一条也是最重要的一条:隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。
下面是a.c的内容
char a = 'A'; // global variable
void msg()
{
printf("Hello/n");
}
下面是main.c的内容
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序的运行结果是:
A Hello
你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。
#i nclude <stdio.h>
int fun(void){
static int count = 10; //事实上此赋值语句从来没有执行过
return count--;
}
int count = 1;
int main(void)
{
printf("global/t/tlocal static/n");
for(; count <= 10; ++count)
printf("%d/t/t%d/n", count, fun());
return 0;
}
程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
(3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加&rsquo;/0&rsquo;太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是&rsquo;/0&rsquo;。不妨做个小实验验证一下。
#i nclude <stdio.h>
int a;
int main(void)
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
程序的运行结果如下
integer: 0; string: (begin)(end)
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
   
 
  
  ////////////////////////////////////////////////////////////////
  
  /*
   * 使用 Visual C++ 6.0
   * 编译器:Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86
   * 链接器:Microsoft (R) Incremental Linker Version 6.00.8168
   *
   * 程序是 release 版本
   */
  
  int global_a;
  static int global_b;
  
  int main(int argc, char* argv[])
  {
   int local_a;
   static int local_b;
  
   printf("local_b is at /t%p/n", &local_b);
   printf("global_b is at /t%p/n", &global_b);
   printf("global_a is at /t%p/n", &global_a);
   printf("local_a is at /t%p/n", &local_a);
  
   return 0;
  }
  
  编译运行的结果是:
  local_b is at 00406910
  global_b is at 00406914
  global_a is at 00406918
  local_a is at 0012FF80
  
  ///////////////////////////////////////////////////////////////
  
  从结果我们可以很直观地看到 global_a,global_b,local_b 在可执行程序中的位置是连续的,根据我们前面的介绍,section是页面对齐的,我们可以得知,这三个变量处于同一个 section。这就是 static 局部变量具有“记忆功能”的根本原因,因为编译器将local_b 当作一个全局变量来看待的。而变量 local_a 才是在栈上分配的,所以他的地址离其他几个变量很远。
  
  那么它们的差别又是什么呢?
  我们使用 Visual C++ 6.0 中自带的 dumpbin 程序察看一下 obj 文件的符号表:
  D:/test000/Release> dumpbin /SYMBOLS test000.obj
  
  结果如下:(我只摘出了和我们论题有关系的部分)
  External | _global_a
  Static | _global_b
  Static | _?local_b@?1??main@@9@9
  
  从中我们可以看出:
  1. static 变量同样出现在符号表中。
  2. static 变量的属性和普通的全局变量不同。
  3. local_b 似乎变成了一个很奇怪的名字 _?local_b@?1??main@@9@9
  
  第一点,只有全局变量才会出现在符号表中,所以毫无疑问 local_b 被编译器看作是全局变量。
  第二点,External属性表明,该符号可以被其他的 obj 引用(做链接),static 属性表明该符号不可以被其他的 obj 引用,所以所谓 static 变量不能被其他文件引用不仅仅是在 C 语法中做了规定,真正的保证是靠链接器完成的。
  第三点,_?local_b@?1??main@@9@9 就是 local_b,链接器为什么要换名字呢?很简单,如果不换名字,如果有一个全局变量与之重名怎么办,所以这个变量的名字不但改变了,而且还有所在函数的名字作为后缀,以保证唯一性。
  
  
  二:volatile
  
  说道 volatile 这个关键字,恐怕有些人会想,这玩意儿是 C 语言的关键字么?你老兄不会忽悠俺吧?嘿嘿,这个关键字确实是C语言的关键字,只不过比较少用,他的作用很奇怪:编译器,不要给我优化用这个关键字修饰的符号所涉及的程序。有看官会说这不是神经病么?让编译器优化有什么不好,我巴不得自己用的编译器优化算法世界第一呢。
  
  好,我们来看几个例子:(Microsoft Visual C++ 6.0, 程序编译成 release 版本)
  
  例一:
  
  /*
   * 第一个程序
   */
  int main(int argc, char* argv[])
  {
   int i;
  
   for (i = 0; i < 0xAAAA; i++);
  
   return 0;
  }
  
  /*
   * 汇编码
   */
  _main:
  00401011 xor eax,eax
  00401013 ret
  
  通过观察这个程序的汇编码我们发现,编译器发现程序的执行结果不会影响任何寄存器变量,就将这个循环优化掉了,我们在汇编码里面没有看到任何和循环有关的部分。这两句汇编码仅仅相当于 return 0;
  
  /*
   * 第二个程序
   */
  int main(int argc, char* argv[])
  {
   volatile int i;
  
   for (i = 0; i < 0xAAAA; i++);
  
   return 0;
  }
  
  /*
   * 汇编码
   */
  _main:
  00401010 push ebp
  00401011 mov ebp,esp
  00401013 push ecx
  00401015 mov dword ptr [ebp-4],0
  0040101C mov eax,0AAAAh
  00401021 mov ecx,dword ptr [ebp-4]
  00401024 cmp ecx,eax
  00401026 jge _main+26h (00401036)
  00401028 mov ecx,dword ptr [ebp-4]
  0040102B inc ecx
  0040102C mov dword ptr [ebp-4],ecx
  0040102F mov ecx,dword ptr [ebp-4]
  00401032 cmp ecx,eax
  00401034 jl _main+18h (00401028)
  00401036 xor eax,eax
  00401038 mov esp,ebp
  0040103A pop ebp
  0040103B ret
  
  我们用 volatile 修饰变量 i,然后重新编译,得到的汇编码如上所示,这回好了,循环回来了。有人说,这有什么意义呢,这个问题问得好。在通常的应用程序中,这个小小的延迟循环通常没有用,但是写过驱动程序的朋友都知道,有时候我们写外设的时候,两个命令字之间是需要一些延迟的。
  
  
  
  例二:
  
  /*
   * 第一个程序
   */
  int main(int argc, char* argv[])
  {
   int *p;
  
   p = 0x100000;
   *p = 0xCCCC;
   *p = 0xDDDD;
  
   return 0;
  }
  
  /*
   * 汇编码
   */
  _main:
  00401011 mov eax,100000h
  00401016 mov dword ptr [eax],0DDDDh
  0040101C xor eax,eax
  0040101E ret
  
  这个程序中,编译器认为 *p = 0xCCCC; 没有任何意义,所以被优化掉了。
  
  /*
   * 第二个程序
   */
  int main(int argc, char* argv[])
  {
   volatile int *p;
  
   p = 0x100000;
   *p = 0xCCCC;
   *p = 0xDDDD;
  
   return 0;
  }
  
  /*
   * 汇编码
   */
  _main:
  00401011 mov eax,100000h
  00401016 mov dword ptr [eax],0CCCCh
  0040101C mov dword ptr [eax],0DDDDh
  00401022 xor eax,eax
  00401024 ret
  
  重新声明这个变量,*p = 0xCCCC; 被执行了,同样,这主要用于驱动外设,有的外设要求连续向一个地址写入多个不同的数据,才能外成一个完整的操作。有的群众迷惑了,为啥驱动外设要写内存啊?我估计那是您仅仅了解 Intel 的 CPU,Intel 的CPU 外设地址和内存地址分开,访问外设的时候使用特殊指令,比如 inb, outb。但有一些 CPU 比如 ARM,外设是和内存混合编址的,访问外设看上去就像读写普通的内存。具体细节请参考 Intel 和 ARM 的手册。
  
  大家注意到一个精彩的细节了么,在例二中编译器发现一个寄存器 eax 可以完成整个函数,所以并没有给变量 p 在栈上分配内存。省略了栈的操作,节省了不少时间,通常栈的操作使用 5 条以上的汇编码。
  
  又有群众反映了,你说的两个例子,都是写驱动程序用到的,我写应用程序是不是就没必要知道了呢?不是,请您继续看下面的例子:
  
  例三:
  
  int run = 1;
  
  void t1()
  {
   run = 0;
  }
  
  void t2()
  {
   while (run)
   {
   printf("error ./n");
   }
  }
  
  int main(int argc, char* argv[])
  {
   t1();
   t2();
  
   return 0;
  }
  
  这个程序乍看没什么问题,在某些编译器,或者某些优化参数下,while (run)会被优化成一个死循环,是因为编译器不知道会有外部的线程要改变这个变量,当他看到 run 的定义时,认为这个循环永远不会退出,所以直接将这个循环优化成了死循环。解决的办法是用 volatile 声明变量 i。
  
  我手头的编译器这个例子不会出错,我也懒得找让这个例子成立的编译器了,大家自己动手试验一下看看吧。
  
  如果大家静下心来读上一些汇编码就会发现,微软的编译器经常会有一些精彩的表现。哎,拍微软的马屁也没用,给微软发过简历,连面试的机会都没给,就被拒了,太郁闷了!
 

staticvolatile、以及二者混合使用的用法 阅读详情

相关推荐

staticVolatile 的区别

static也是各个业务方可以去全局修改; volatile是处理多线程锁的替代方案,对应有时需要实时的修改共享资源的变量,被volatile修复的变量的值可以立刻被业务方取得最新的值。 不过,猛地感觉,nnd,这不是一样么,static是静态的,所以理论上也可以在不同线程去访问,能访问也就是能修改,所以这里老穆在网上搜了搜 相关的资料,把这个知识点在加强下: 变量放在主存区上,使用该变量的每个线程,都将从主存区拷贝一份到自己的工作区上进行操作。 volatile, 声明这个字段易变(可能被多个线程使.

licux的博客 4109

staticvolatile的使用

1.static保证唯一性,就是在主内存中是唯一的变量。 2.volatile是保证可见性,就是指在工作线程和主内存的数据的一致性,改变了工作线程中volatile修饰的变量,那么主内存也要发生更新。 所以,volatilestatic一起使用不矛盾。因为static修饰只能保证在主内存的唯一性,如果涉及到其他工作线程,改变参数可能就会导致static修饰的变量的内容无法同步,所以stati...

hacker_crazy的专栏 1万+

详解staticvolatile、const

1、背景 在查阅相关资料的时候,无意间看到一个大佬对于static关键字的讲解,如雷贯耳,写得非常容易理解,这是大佬的链接 本人在学习相关知识的时候,喜欢也习惯把从各种书籍或者是各位大佬的博客中学到的知识用自己的逻辑和自己的语言重新组织一下(当然,如果书中或者各位大佬的描述我觉得非常好理解,我就直接使用啦),方便自己记忆,加深影响。 废话不多说,我们进入正题 2、static关键字介绍 在记录知识点的时候,我喜欢以提问的方式来一层一层地拨开这个知识点的“心”,我个人觉得这样的方式来学习某些知识,能够形成一个

wyttRain的博客 3022

staticvolatile的用法

volatile volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。 使用该关键字的例子如下: int volatile nVint; 当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 例如: volatile int i=10

极客神殿 4019

staticVolatile

static在C语言中的三种用法 1.给局部变量加static会延长static的生命周期,但不会改变他的作用域 2.static修饰全局变量,会改变全局变量的作用局,阻止其在整个工程的使用,仅仅能在该文件中使用。 3.修饰函数和修饰全局变量一样。 指针的用法 取地址——&, 打印地址用%p , *+地址名—取该地址的空间 例子:修改const修饰的变量 但是要注意在C++环境下,c...

Shadow_______I的博客 415

Difference Between Static and Volatile

source: http://stackoverflow.com/questions/2423622/volatile-vs-static-in-java Difference Between Static and Volatile : Static Variable: If two Threads(suppose t1 and t2) are accessing the same

tech 356

volatile, extern and static

volatile :   1,该关键字只是在编译时指使 编译器 作出相应动作;而非运行时作怪;   2,指示编译器,不必作出多余的优化: 对于已经存入寄存器当中的变量作访问时,使用volatile会在编译得到的指令当中增加 访问内存的 部分; 即,当访问处于寄存器变量时,都是去主存获取当前最新的数据;   3,与cache一致性无关; cache一致性由硬件维护;   4,Useful l...

weixin_30416497的博客 68

Dynamic/Static/Reinterpret/Const and Volatile Cast

Const and Volatile Cast The expression const_cast(v) can be used to change the "const" or "volatile" qualifiers of pointers or references. T must be a pointer, reference, or pointer to member type

记事本 612

Dynamic/Static/Reinterpret/Const and Volatile Cast (English)

Const and Volatile Cast The expression const_cast(v) can be used to change the "const" or "volatile" qualifiers of pointers or references. T must be a pointer, reference, or pointer to member type

Qsir的专栏 550

static,const,volatile,extern,register关键字深入解析

static,const,volatile等关键字深入解析,以及volatile在STM32中的运用

k666499436的博客 6310

C语言const、static、extern、volatile关键字总结

一、const 关键字总结: 作为一个程序员,我们看到关键字const时,首先想到的应该是:只读。因为,它要求其所修饰的对象为常量,不可对其修改和二次赋值操作(不能作为左值出现)。看几个例子的中const作用: 1. 修饰常量 用const修饰的变量是不可变的,对const变量赋值:一是在声明时对变量初始化,二是在函数中声明const形参,函数调用时会得到实参的值。以下两种定义形式在本质上是...

心中的日月的博客 5130

staticvolatile、const关键字

static中文字意是静态的,用于控制变量和函数的作用域、生命周期和可见性等,修饰变量和函数,将其变成静态变量/函数。static总体来说有以下作用:1、在函数中声明变量时, static 关键字指定变量只初始化一次,并在之后调用该函数时保留其状态。2、在声明变量时,变量具有静态持续时间,并且除非您指定另一个值。3、在全局和命名空间范围 (在单个文件范围内声明变量或函数时) static 关键字指定变量或函数为内部链接,即外部文件无法引用该变量或函数。

2302_77530416的博客 1213

volatile static 用法

工作中看到了 volatile static a; 的用法,个人大致理解是:static使得变量线程安全,volatile使得变量动态改变后可在任意线程同步。   以下内容为转载: static 1、概述   static 声明的变量在C语言中有两方面的特征:     1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的...

martinue 6416

[C语言]const, staticvolatile

​c语言,const,staticvolatile关键字详解

月光之傲的博客 1746

staticvolatile的原理及区别,看这篇就够了

static 表示变量在方法区中,所有线程共享。 JVM内存结构,把内存分为 堆、栈、方法区、本地方法栈和程序计数器。其中堆和栈被所有线程共享,在内存模型中被成为主内存,而栈、程序计数器属于线程私有,在内存模型中被成为工作内存。 总结JVM内存= 堆+栈+方法区+本地方法栈+程序计数器 主内存 = 堆+方法区 (所有线程共享) 工作内存 = 栈+程序计数器 (线程私有)前面说到static在方法区中,那有两个线程,一个去写,一个去读,是不是就能马上读到最新的值呢...

编程技术分享,Java后端、Vue前端 5663

const,、staticvolatile关键字

const,、staticvolatile详解

san__Yy的博客 1321

static、extern、volatile的用法

static的用法: 静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。 静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。 用法1:函数内部声明的static变量,可作为对象间的一种通信机制 如果一局部变量被声明为static,那么将只有唯一的一个静态分配的对象,它被用于在该函数的所有调用中表示这个变量。这个对象将只在

人在旅途/章鸽 的专栏 3912
上一篇: linux HZ Tick Jiffies
下一篇: inline 函数
bdc995
博客等级 码龄19年 8粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值