关于全局变量被修改以及volatile的用法

 

 

.....

unsigned char num=0;

.....

INTERRUPT()

{

....

num++;

....

}

void main()

{

out(num);

}

很不幸的事情是在主函数中,num一直都不会变,编译器avrstdio,外部中断。

调试中发现中断时可以进去的,然而中断出来以后,这个全局变量就被改变了,后来加了volitale就可以了。。

下面说说volatile的具体用法(以下内容都是摘抄):

volatile 的字面意思为不稳定的,易变的

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。

用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。

没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)。

以上两种情况的区别在于被编译成汇编代码之后,两者是不一样的。之所以这样做是因为变量i可能会经常变化,保证对特殊地址的稳定访问。

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

使用该关键字的例子如下:
volatile int i;
当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存地址读取数据,即使它前面的指令刚刚从该处读取过数据,而且读取的数据立刻被保存。

例如:
volatile int i = 10;
int a = i;
...
//其他代码,并未明确告诉编译器,对i进行过操作
int b = i;

volatile指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化的做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中,而不是重新从i的地址中读取。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。

注意,在VC6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:

首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
 
#include <stdio.h>

void main() {
int i = 10;
int a = i;
printf("i = %d\n", a);
// 下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}

int b = i;
printf("i = %d\n", b);
}

然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32

然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10

输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。

下面,我们把i的声明加上volatile关键字,看看有什么变化:

#include <stdio.h>

void main() {
volatile int i = 10;
int a = i;
printf("i = %d\n", a);
// 下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}

int b = i;
printf("i = %d\n", b);
}

分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32

这说明这个关键字发挥了它的作用!

================================

将一个变量说明为volatile表示这个变量是“易变的”。如果一个变量会被其它引用改变,或在其它并行的任务中会被改变(例如中断服务程序),都要显式地说明为“volatile”,否则在编译器优化阶段会作出错误的判断,例如将这个变量读入寄存器以后,在没有对这个变量赋值以前,会一直使用寄存器中的值,而实际上这个变量的值可能已经被一个指针引用改变了,或者是在中断服务程序中被改变了,下面这个例子说明了这种错误:

有一个变量T,在定时中断中每隔一个固定时间减一,然后在主程序中等待它减到0。

unsigned char T;
void T0_int(void) interrupt 1 {
...
T--;
...
}

void main(void)
{
...
T=10;
while (T!=0);
...
}

正确的写法应该是将第一句改为:
volatile unsigned char T;

从上面这个例子中很不幸的看到,在中断中改变的这个数值如果不申请成volatile的话,被优化以后就麻烦了。。。

这个在嵌入式环境中尤其重要。。好吧。。难。。以后要注意一下~~~~~

2011.5.26新加:

例子:串口通过中断接受一个字节数据,然后在中断中把这个数据存到一个缓冲区中,等待使用。当主程序中需要读出数据时,就去读缓冲区。此时缓冲区的声明应该是

volitale unsigned char rxbuf[SIZE]; 需要将关键字加上,否则缓冲区中读出的数据可能不会对,第一个字节来的时候,再加一个延时,等待数据完成即可。

<think>好的,我现在需要回答用户关于程序运行时全局变量是否会被重新赋值的问题。首先,我得理解用户的问题。用户可能是在编程过程中遇到全局变量被意外修改的情况,或者想知道如何确保全局变量的值在程序运行期间不被改变。 根据引用内容,特别是引用[3],全局变量在程序中被用来储存各种值,比如传感器数据或执行器的状态。这些变量在程序的不同部分都可能被访问或修改。用户的问题核心在于了解在程序运行期间,全局变量是否会被重新赋值,以及这背后的机制。 接下来,我需要结合已有的引用信息来构建答案。引用[1]提到全局常量传播可能导致逻辑错误,特别是当程序依赖非常量值时,这说明如果全局变量没有被正确保护,可能会被编译器优化或在运行时被修改引用[2]则指出,为了避免竞态条件,需要使用功能全局变量来保护访问操作,确保每次只有一个操作可以修改变量,这暗示全局变量在并发环境下可能被多个线程或进程修改,导致重新赋值。引用[3]解释了全局变量的抽象概念及其在程序中的映射关系,进一步说明全局变量的可变性。 我需要分情况讨论:单线程和多线程环境。在单线程中,全局变量是否被重新赋值取决于代码逻辑。如果有代码显式修改它,就会变化。而在多线程环境下,如果没有同步机制,多个线程可能同时修改全局变量,导致不可预期的结果。此外,编译器优化如全局常量传播可能意外改变变量行为,特别是当变量被误认为是常量时。 还要提到如何防止全局变量被重新赋值,比如使用常量声明(如C中的const,Java中的final),或者通过访问控制(如私有变量和getter方法)。同时,引用[2]建议使用功能全局变量保护,避免竞态条件。 需要确保回答结构清晰,先明确回答“是”,然后分情况讨论,最后给出防护措施。同时,根据用户要求生成相关问题,可能涉及如何避免修改、多线程处理、编译器优化的影响等。 最后,检查是否所有LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,但这个问题可能不涉及数学公式,所以可能不需要。但如果有提到变量名或代码,要用代码块格式。同时确保引用标识正确添加,如引用[1]、[2]、[3]在适当的位置。 总结回答结构: 1. 明确回答:全局变量可能被重新赋值。 2. 分情况讨论:单线程和多线程环境。 3. 编译器优化的影响。 4. 如何防止被修改。 5. 相关问题。</think>### 程序运行期间全局变量是否会被重新赋值? 是的,程序在运行期间全局变量可能会被重新赋值。这取决于代码逻辑、编程语言的特性以及运行环境(如单线程或多线程)[^3]。以下分情况说明: --- #### 1. **单线程环境** 在单线程程序中,全局变量是否被重新赋值取决于是否有显式的代码逻辑对其进行修改: - **直接赋值**:若代码中存在对全局变量的赋值操作(例如 `global_var = new_value`),则会触发重新赋值。 - **函数调用修改**:若函数通过作用域声明(如 `global` 关键字)或指针操作修改全局变量,也会导致其值变化。 **示例代码**: ```python global_var = 10 # 初始值 def update_global(): global global_var global_var = 20 # 显式重新赋值 update_global() print(global_var) # 输出 20 ``` --- #### 2. **多线程环境** 在多线程或并发场景中,全局变量可能因**竞态条件(Race Condition)**被多个线程同时修改,导致不可预期的结果[^2]: - **无同步机制**:若未使用锁(Lock)或原子操作保护全局变量,多个线程的交叉执行可能导致赋值冲突。 - **功能全局变量保护**:如引用[2]所述,通过功能全局变量(如单例模式或线程安全容器)限制每次仅一个操作修改变量,可避免冲突。 --- #### 3. **编译器优化的影响** 某些编译器优化(如全局常量传播)可能改变变量的行为: - **误判为常量**:若编译器错误地将全局变量识别为常量(如未使用 `volatile` 修饰),可能优化掉对其的赋值操作,导致逻辑错误[^1]。 - **解决方案**:使用 `volatile` 关键字(如C/C++)或避免过度依赖编译器优化。 --- #### 4. **如何防止全局变量被重新赋值** - **声明为常量**:使用 `const`(C/C++)、`final`(Java)或只读属性。 - **封装访问**:通过私有变量和 `getter` 方法限制直接修改。 - **线程同步**:在多线程中使用锁或原子操作。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值