posts - 19,  comments - 21,  trackbacks - 0
一个定义为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 阅读(299) | 评论 (0)编辑 收藏
   诸位,咱当电子工程师也是十余年了,不算有出息,环顾四周,也没有看见几个有出息的!回顾工程师生涯,感慨万千,愿意讲几句掏心窝子的话,也算给咱们师弟师妹们提个醒,希望他们比咱们强!
 
[1]好好规划自己的路,不要跟着感觉走!根据个人的理想决策安排,绝大部分人并不指望成为什么院士或教授,而是希望活得滋润一些,爽一些。那么,就需要慎重安排自己的轨迹。从哪个行业入手,逐渐对该行业深入了解,不要频繁跳槽,特别是不要为了一点工资而转移阵地,从长远看,这点钱根本不算什么,当你对一个行业有那么几年的体会,以后钱根本不是问题。频繁地动荡不是上策,最后你对哪个行业都没有摸透,永远是新手!
 
[2]可以做技术,切不可沉湎于技术。千万不可一门心思钻研技术!给自己很大压力,如果你的心思全部放在这上面,那么注定你将成为孔乙己一类的人物!适可而止为之,因为技术只不过是你今后前途的支柱之一,而且还不是最大的支柱。
 
[3]不要去做技术高手,只去做综合素质高手!在企业里混,我们时常瞧不起某人,说他“什么都不懂,凭啥拿那么多钱,凭啥升官!”这是普遍的典型的工程师的迂腐之言。8051很牛吗?人家能上去必然有他的本事,而且是你没有的本事。你想想,老板搞经营那么多年,难道见识不如你这个新兵?人家或许善于管理,善于领会老板意图,善于部门协调等等。因此务必培养自己多方面的能力,包括管理,亲和力,察言观色能力,攻关能力等,要成为综合素质的高手,则前途无量,否则只能躲在角落看示波器!技术以外的技能才是更重要的本事!!从古到今,美国日本,一律如此!
 
[4]多交社会三教九流的朋友!不要只和工程师交往,认为有共同语言,其实更重要的是和其他类人物交往,如果你希望有朝一日当老板或高层管理,那么你整日面对的就是这些人。了解他们的经历,思维习惯,爱好,学习他们处理问题的模式,了解社会各个角落的现象和问题,这是以后发展的巨大的本钱。
 
[6]抓住时机向技术管理或市场销售方面的转变!要想有前途就不能一直搞开发,适当时候要转变为管理或销售,前途会更大,以前搞技术也没有白搞,以后还用得着。搞管理可以培养自己的领导能力,搞销售可以培养自己的市场概念和思维,同时为自己以后发展积累庞大的人脉!应该说这才是前途的真正支柱!
 
[7]逐渐克服自己的心里弱点和性格缺陷!多疑,敏感,天真(贬义,并不可爱),犹豫不决,胆怯,多虑,脸皮太薄,心不够黑,教条式思维。。。这些工程师普遍存在的性格弱点必须改变!很难吗?只在床上想一想当然不可能,去帮朋友守一个月地摊,包准有效果,去实践,而不要只想!不克服这些缺点,一切不可能,甚至连项目经理都当不好--尽管你可能技术不错!
 
[8]工作的同时要为以后做准备!建立自己的工作环境!及早为自己配置一个工作环境,装备电脑,示波器(可以买个二手的),仿真器,编程器等,业余可以接点活,一方面接触市场,培养市场感觉,同时也积累资金,更重要的是准备自己的产品,咱搞技术的没有钱,只有技术,技术的代表不是学历和证书,而是产品,拿出象样的产品,就可技术转让或与人合作搞企业!先把东西准备好,等待机会,否则,有了机会也抓不住!
 
[9]要学会善于推销自己!不仅要能干,还要能说,能写,善于利用一切机会推销自己,树立自己的品牌形象,很必要!要创造条件让别人了解自己,不然老板怎么知道你能干?外面的投资人怎么相信你?提早把自己推销出去,机会自然会来找你!搞个个人主页是个好注意!!特别是培养自己在行业的名气,有了名气,高薪机会自不在话下,更重要的是有合作的机会......

[10]该出手时便出手!永远不可能有100%把握!!!条件差不多就要大胆去干,去闯出自己的事业,不要犹豫,不要彷徨,干了不一定成功,但至少为下一次冲击积累了经验,不干永远没出息,而且要干成必然要经历失败。不经历风雨,怎么见彩虹,没有人能随随便便成功!
posted @ 2006-04-11 10:51 halCode 阅读(503) | 评论 (2)编辑 收藏

借用别人的流程图提醒自己编程的步骤


1.面向连接的套接字的系统调用时序图



无连接协议的套接字调用时序图

面向连接的应用程序流程图



posted @ 2006-03-23 21:11 halCode 阅读(6219) | 评论 (0)编辑 收藏
大学生=吃饭+睡觉+谈恋爱
猪=吃饭+睡觉
所以:大学生=猪+谈恋爱
以上推出 大学生-谈恋爱=猪
即 大学生不谈恋爱的都是猪 
同理得出 猪只要谈恋爱就可以变成大学生
posted @ 2006-03-21 10:34 halCode 阅读(362) | 评论 (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 阅读(622) | 评论 (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 阅读(2631) | 评论 (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 阅读(2840) | 评论 (0)编辑 收藏
成功的人要具备:有肚量去容忍那些不能改变的事,有勇气去改变那些可能改变的事,有智慧去区别上述两类事。
posted @ 2005-12-29 14:17 halCode 阅读(313) | 评论 (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)
{
  
*= (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 *= (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 阅读(483) | 评论 (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 阅读(514) | 评论 (0)编辑 收藏
仅列出标题  下一页

<2024年3月>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

常用链接

留言簿(7)

随笔分类

随笔档案

文章分类

文章档案

相册

编程资源

不务正业

找工作

搜索

  •  

最新评论

阅读排行榜

评论排行榜