一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
讲讲我的理解: (欢迎打板子...~~!)
关键在于两个地方:
1. 编译器的优化 (请高手帮我看看下面的理解)
在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;
当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致
举一个不太准确的例子:
发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资
员工 -- 原始变量地址
银行卡号 -- 原始变量在寄存器的备份
2. 在什么情况下会出现(如1楼所说)
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;
“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;
而用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了; 大家看看前面那种解释(易变的)是不是在误导人
------------简明示例如下:-----------------
-
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int volatile nVint;
>>>>当要求使用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文件,输入下面的代码:
>>
#i nclude <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d",b);
}
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#i nclude <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d",b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!
------------------------------------
volatile对应的变量可能在你的程序本身不知道的情况下发生改变
比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
你自己的程序,是无法判定合适这个变量会发生变化
还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
--------------------------------------------------------------------------------
典型的例子
for ( int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成
for ( volatile int i=0; i<100000; i++);
它就会执行了
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
posted @
2008-07-27 21:56 halCode 阅读(313) |
评论 (0) |
编辑 收藏
诸位,咱当电子工程师也是十余年了,不算有出息,环顾四周,也没有看见几个有出息的!回顾工程师生涯,感慨万千,愿意讲几句掏心窝子的话,也算给咱们师弟师妹们提个醒,希望他们比咱们强!
[1]好好规划自己的路,不要跟着感觉走!根据个人的理想决策安排,绝大部分人并不指望成为什么院士或教授,而是希望活得滋润一些,爽一些。那么,就需要慎重安排自己的轨迹。从哪个行业入手,逐渐对该行业深入了解,不要频繁跳槽,特别是不要为了一点工资而转移阵地,从长远看,这点钱根本不算什么,当你对一个行业有那么几年的体会,以后钱根本不是问题。频繁地动荡不是上策,最后你对哪个行业都没有摸透,永远是新手!
[2]可以做技术,切不可沉湎于技术。千万不可一门心思钻研技术!给自己很大压力,如果你的心思全部放在这上面,那么注定你将成为孔乙己一类的人物!适可而止为之,因为技术只不过是你今后前途的支柱之一,而且还不是最大的支柱。
[3]不要去做技术高手,只去做综合素质高手!在企业里混,我们时常瞧不起某人,说他“什么都不懂,凭啥拿那么多钱,凭啥升官!”这是普遍的典型的工程师的迂腐之言。8051很牛吗?人家能上去必然有他的本事,而且是你没有的本事。你想想,老板搞经营那么多年,难道见识不如你这个新兵?人家或许善于管理,善于领会老板意图,善于部门协调等等。因此务必培养自己多方面的能力,包括管理,亲和力,察言观色能力,攻关能力等,要成为综合素质的高手,则前途无量,否则只能躲在角落看示波器!技术以外的技能才是更重要的本事!!从古到今,美国日本,一律如此!
[4]多交社会三教九流的朋友!不要只和工程师交往,认为有共同语言,其实更重要的是和其他类人物交往,如果你希望有朝一日当老板或高层管理,那么你整日面对的就是这些人。了解他们的经历,思维习惯,爱好,学习他们处理问题的模式,了解社会各个角落的现象和问题,这是以后发展的巨大的本钱。
[6]抓住时机向技术管理或市场销售方面的转变!要想有前途就不能一直搞开发,适当时候要转变为管理或销售,前途会更大,以前搞技术也没有白搞,以后还用得着。搞管理可以培养自己的领导能力,搞销售可以培养自己的市场概念和思维,同时为自己以后发展积累庞大的人脉!应该说这才是前途的真正支柱!
[7]逐渐克服自己的心里弱点和性格缺陷!多疑,敏感,天真(贬义,并不可爱),犹豫不决,胆怯,多虑,脸皮太薄,心不够黑,教条式思维。。。这些工程师普遍存在的性格弱点必须改变!很难吗?只在床上想一想当然不可能,去帮朋友守一个月地摊,包准有效果,去实践,而不要只想!不克服这些缺点,一切不可能,甚至连项目经理都当不好--尽管你可能技术不错!
[8]工作的同时要为以后做准备!建立自己的工作环境!及早为自己配置一个工作环境,装备电脑,示波器(可以买个二手的),仿真器,编程器等,业余可以接点活,一方面接触市场,培养市场感觉,同时也积累资金,更重要的是准备自己的产品,咱搞技术的没有钱,只有技术,技术的代表不是学历和证书,而是产品,拿出象样的产品,就可技术转让或与人合作搞企业!先把东西准备好,等待机会,否则,有了机会也抓不住!
[9]要学会善于推销自己!不仅要能干,还要能说,能写,善于利用一切机会推销自己,树立自己的品牌形象,很必要!要创造条件让别人了解自己,不然老板怎么知道你能干?外面的投资人怎么相信你?提早把自己推销出去,机会自然会来找你!搞个个人主页是个好注意!!特别是培养自己在行业的名气,有了名气,高薪机会自不在话下,更重要的是有合作的机会......
[10]该出手时便出手!永远不可能有100%把握!!!条件差不多就要大胆去干,去闯出自己的事业,不要犹豫,不要彷徨,干了不一定成功,但至少为下一次冲击积累了经验,不干永远没出息,而且要干成必然要经历失败。不经历风雨,怎么见彩虹,没有人能随随便便成功!
posted @
2006-04-11 10:51 halCode 阅读(519) |
评论 (2) |
编辑 收藏
借用别人的流程图提醒自己编程的步骤
1.面向连接的套接字的系统调用时序图
无连接协议的套接字调用时序图
面向连接的应用程序流程图
posted @
2006-03-23 21:11 halCode 阅读(6295) |
评论 (0) |
编辑 收藏
大学生=吃饭+睡觉+谈恋爱
猪=吃饭+睡觉
所以:大学生=猪+谈恋爱
以上推出 大学生-谈恋爱=猪
即 大学生不谈恋爱的都是猪
同理得出 猪只要谈恋爱就可以变成大学生
posted @
2006-03-21 10:34 halCode 阅读(372) |
评论 (0) |
编辑 收藏
“首先要感谢很多在场和不在场的人,像制作人Ann Li,编剧Diana,Jack~~我觉得《断背山》是属于我李安一个人的,如果被其他人拍了,我将会很生气!这部电影既不是关于同性恋,女人,爱情,而是爱的本身。
谢谢大家的支持,谢谢我的妻子和儿子,我爱你们,我将永远和你们同在。虽然在期间我的父亲不幸过世,但是要感谢我家中的妈妈,还有中国台湾,香港和内地的同胞们。最后,谢谢大家的关心!”
看到这段话时,我十分感动,尤其是“但是要感谢我家中的妈妈,还有中国台湾,香港和内地的同胞们。”体现了一种难得的亲情,而且在拿到国际电影上最高荣誉的奖项时,不忘记自己是名中国人,不忘记把台湾说成“中国台湾”。
然而我注意到这句话的表述有点怪怪的,是不是他自己的原话呢?我最不愿意怀疑的就是“中国台湾”几个字。于是我查看了sohu这段新闻下网友的评论,发现原文是这样的:“And finally, to my mother and family, and everybody in Taiwan, Hong Kong and China. Thank you. ”,他说的是“台湾、香港和中国”。。。。。
尽管他是所有华人的骄傲,尽管也许他并没有太在意他的表述,但是。。我多少还是有些失望的。。。。
posted @
2006-03-06 19:59 halCode 阅读(672) |
评论 (0) |
编辑 收藏
昨天写了MoveWindow函数,今天又要写SetWindowPos函数,因为MoveWindow确实不好,浪费了差不多一天时间。。。对于主窗口,选择了Center属性就可以移动位置,不选就不行;窗口资源过大也不能移动;改变后窗口的尺寸大于等于屏幕尺寸的话,在它上面DoModal出来的新窗口不能移动。。原来,使用MoveWindow移动对话框位置似乎有很苛刻的条件,一不小心就原地不动了,而且我也实在琢磨不透这样的条件,网上也没有查处来。今天用了SetWindowPos后,才发现,果然好用!
BOOL SetWindowPos(
const CWnd* pWndInsertAfter,
int x,
int y,
int cx,
int cy,
UINT nFlags
);
Parameters
- pWndInsertAfter
- Identifies the CWnd object that will precede this CWnd object in the Z-order. This parameter can be a pointer to a CWnd or a Pointer to one of the following values:
- wndBottom Places the window at the bottom of the Z-order. If this CWnd is a topmost window, the window loses its topmost status; the system places the window at the bottom of all other windows.
- wndTop Places the window at the top of the Z-order.
- wndTopMost Places the window above all nontopmost windows. The window maintains its topmost position even when it is deactivated.
- wndNoTopMost Repositions the window to the top of all nontopmost windows (that is, behind all topmost windows). This flag has no effect if the window is already a nontopmost window.
nFlags
Specifies sizing and positioning options. This parameter can be a combination of the following: 常用的是SWP_SHOWWINDOW
注意:
这里的坐标参数用的是Client坐标,对于子窗口用的是其父窗口的坐标系。然而对于用DoModal()弹出的窗口,如果在资源属性里没有选择Child属性的话,它的父窗口是DeskTop,而不是调用DoModal()的窗口。
posted @
2006-03-01 17:30 halCode 阅读(2692) |
评论 (0) |
编辑 收藏
刚做的那个软件界面很重要,就象是把自己做的一个个控件网上画一样,还要按照实际运行效果来不断调整位置,很麻烦。当时使用MoveWindow()这个函数来控制对话框的大小和位置,不了解参数情况,在网上查了,记得好象放在这里备忘,那时还觉得blog真是方便。结果现在又要用起这个函数却又找不到了!不知道是不是放在这个blog里了。看来blog多了还不方便了。不过不要误会,那些都是以前找到这个blog之前暂时用的,都不太满意。最后觉得还是这里好,hoho,方便。晕,我来写这篇日志是干吗的?这么扯到这么远了。。。不说了,还是把MoveWindow()再贴一下吧。。
BOOL MoveWindow
( HWND hWnd, //Handle to the window
int X, //Specifies the new position of the left side of the window.
int Y, //Specifies the new position of the top of the window.
int nWidth,
int nHeight,
BOOL bRepaint
);
这个是 API函数,如果在MFC中使用CWnd::MoveWindow则不需要第一个参数句柄hWnd,其他相同。
posted @
2006-02-28 15:44 halCode 阅读(2872) |
评论 (0) |
编辑 收藏
成功的人要具备:有肚量去容忍那些不能改变的事,有勇气去改变那些可能改变的事,有智慧去区别上述两类事。
posted @
2005-12-29 14:17 halCode 阅读(323) |
评论 (0) |
编辑 收藏
有些东西在你没有遇到之前还真是不知道自己没有弄清楚,也许是我平时细节的地方注意少了,看来以后要多加注意了。
char *pStr;
char ch;
char str[] = "Hello";
ch = str[1];
*pStr = str[1];
printf("ch = %c\n", ch);
printf("*pStr = %c", *pStr);
此时打印ch值的时候能正确输出,但是打印pStr[0]的时候就出问题了,跟踪发现是pStr[0] = str[1];这句出现问题。
原来,char *pStr; 只定义了一个4字节的指针变量,而这个变量里面的内容是将要指向一个char类型变量的,但是此时pStr只是个“野指针”,并没有指向一个char类型的内存单元,所以,当用*pStr来访问这个元素时,系统根本不知道该访问何处的空间,因此,在使用pStr前,必须让它指向一个具体的空间。
由上面可以将相关语句改为
char *pStr;
char ch;
char str[] = "Hello";
pStr = &ch; //pStr指向ch、获得初始化
pStr = &str[1]; //pStr指向str[1]地址、获得初始化
ch = str[1];
*pStr = str[1];
printf("ch = %c\n", ch);
printf("*pStr = %c", *pStr); 也就是说,要先给指针一个地址值初始化它,然后才能用*访问它指向的内容。
◎另外,在子函数中使用malloc()/new()分配的内存空间不会因子函数的返回而消失,函数只会清理调子函数里定义的变量的空间,如:
char* func()
{
char *p;
p = (char *)malloc(sizeof(char));
return p;
} //返回后,p所在空间被释放,但是p所指空间还存在 所以,可以用p1=func();来获得在func()中分配的空间。
此中方法可以用来解决“指针的指针”使用不方便的问题。步骤分2步:1、把传入的指针的指针参数去掉;2、把函数的返回值赋给要改变的对象的指针:
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, 100); // 注意参数是 &str,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
/**///////下面是用传递动态内存的方法实现char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof(char) * num);
return p;
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
因为此时用malloc/new分配的内存空间是堆里面的,函数返回时不会回收;而如果在子函数中用char p[]="asdfgewq";则p是在栈中定义的(先分配p的空间,然后讲字符串拷贝进去,跟char *p="asdfasdf"分配在全局静态内存中不一样),所以会被系统收回。
posted @
2005-12-21 10:55 halCode 阅读(542) |
评论 (0) |
编辑 收藏
1。定义数组变量时必须指定数组元素个数,因为系统会根据元素个数在编译时一次性分配这么多内存;
而指针变量只需要指定类型,系统只为该指针变量分配4字节(32位机)内存,而不会为该变量所指内容分配内存。
2。指针变量是有存储空间的;而数组名仅仅是一个标号,没有实际存储空间,单单一个数组名就只能表示该数组的第1个元素的地址。int a[10]; 规定&a就等于&a[0]或者a .
3。例如 char str[]= "hello world"和char *p = "hello world"中,数组str是先分配给他元素个数个内存,然后将后面的字符串复制给这个空间(注意,此时应该考虑\0字符);而p的建立过程是先在静态存储区建立常字符串"hello world\0",然后将p指向这个常字符串。所以数组str中元素的内容可以通过重新赋值改变,而p指向的内容不能改变。
posted @
2005-12-18 21:40 halCode 阅读(525) |
评论 (0) |
编辑 收藏